##// END OF EJS Templates
log: remove redundant handling of --limit...
Yuya Nishihara -
r35559:349bcd7f default
parent child Browse files
Show More
@@ -1,3933 +1,3931 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 errno
10 import errno
11 import itertools
11 import itertools
12 import os
12 import os
13 import re
13 import re
14 import tempfile
14 import tempfile
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 )
22 )
23
23
24 from . import (
24 from . import (
25 bookmarks,
25 bookmarks,
26 changelog,
26 changelog,
27 copies,
27 copies,
28 crecord as crecordmod,
28 crecord as crecordmod,
29 dagop,
29 dagop,
30 dirstateguard,
30 dirstateguard,
31 encoding,
31 encoding,
32 error,
32 error,
33 formatter,
33 formatter,
34 graphmod,
34 graphmod,
35 match as matchmod,
35 match as matchmod,
36 mdiff,
36 mdiff,
37 obsolete,
37 obsolete,
38 patch,
38 patch,
39 pathutil,
39 pathutil,
40 pycompat,
40 pycompat,
41 registrar,
41 registrar,
42 revlog,
42 revlog,
43 revset,
43 revset,
44 scmutil,
44 scmutil,
45 smartset,
45 smartset,
46 templatekw,
46 templatekw,
47 templater,
47 templater,
48 util,
48 util,
49 vfs as vfsmod,
49 vfs as vfsmod,
50 )
50 )
51 stringio = util.stringio
51 stringio = util.stringio
52
52
53 # templates of common command options
53 # templates of common command options
54
54
55 dryrunopts = [
55 dryrunopts = [
56 ('n', 'dry-run', None,
56 ('n', 'dry-run', None,
57 _('do not perform actions, just print output')),
57 _('do not perform actions, just print output')),
58 ]
58 ]
59
59
60 remoteopts = [
60 remoteopts = [
61 ('e', 'ssh', '',
61 ('e', 'ssh', '',
62 _('specify ssh command to use'), _('CMD')),
62 _('specify ssh command to use'), _('CMD')),
63 ('', 'remotecmd', '',
63 ('', 'remotecmd', '',
64 _('specify hg command to run on the remote side'), _('CMD')),
64 _('specify hg command to run on the remote side'), _('CMD')),
65 ('', 'insecure', None,
65 ('', 'insecure', None,
66 _('do not verify server certificate (ignoring web.cacerts config)')),
66 _('do not verify server certificate (ignoring web.cacerts config)')),
67 ]
67 ]
68
68
69 walkopts = [
69 walkopts = [
70 ('I', 'include', [],
70 ('I', 'include', [],
71 _('include names matching the given patterns'), _('PATTERN')),
71 _('include names matching the given patterns'), _('PATTERN')),
72 ('X', 'exclude', [],
72 ('X', 'exclude', [],
73 _('exclude names matching the given patterns'), _('PATTERN')),
73 _('exclude names matching the given patterns'), _('PATTERN')),
74 ]
74 ]
75
75
76 commitopts = [
76 commitopts = [
77 ('m', 'message', '',
77 ('m', 'message', '',
78 _('use text as commit message'), _('TEXT')),
78 _('use text as commit message'), _('TEXT')),
79 ('l', 'logfile', '',
79 ('l', 'logfile', '',
80 _('read commit message from file'), _('FILE')),
80 _('read commit message from file'), _('FILE')),
81 ]
81 ]
82
82
83 commitopts2 = [
83 commitopts2 = [
84 ('d', 'date', '',
84 ('d', 'date', '',
85 _('record the specified date as commit date'), _('DATE')),
85 _('record the specified date as commit date'), _('DATE')),
86 ('u', 'user', '',
86 ('u', 'user', '',
87 _('record the specified user as committer'), _('USER')),
87 _('record the specified user as committer'), _('USER')),
88 ]
88 ]
89
89
90 # hidden for now
90 # hidden for now
91 formatteropts = [
91 formatteropts = [
92 ('T', 'template', '',
92 ('T', 'template', '',
93 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
93 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
94 ]
94 ]
95
95
96 templateopts = [
96 templateopts = [
97 ('', 'style', '',
97 ('', 'style', '',
98 _('display using template map file (DEPRECATED)'), _('STYLE')),
98 _('display using template map file (DEPRECATED)'), _('STYLE')),
99 ('T', 'template', '',
99 ('T', 'template', '',
100 _('display with template'), _('TEMPLATE')),
100 _('display with template'), _('TEMPLATE')),
101 ]
101 ]
102
102
103 logopts = [
103 logopts = [
104 ('p', 'patch', None, _('show patch')),
104 ('p', 'patch', None, _('show patch')),
105 ('g', 'git', None, _('use git extended diff format')),
105 ('g', 'git', None, _('use git extended diff format')),
106 ('l', 'limit', '',
106 ('l', 'limit', '',
107 _('limit number of changes displayed'), _('NUM')),
107 _('limit number of changes displayed'), _('NUM')),
108 ('M', 'no-merges', None, _('do not show merges')),
108 ('M', 'no-merges', None, _('do not show merges')),
109 ('', 'stat', None, _('output diffstat-style summary of changes')),
109 ('', 'stat', None, _('output diffstat-style summary of changes')),
110 ('G', 'graph', None, _("show the revision DAG")),
110 ('G', 'graph', None, _("show the revision DAG")),
111 ] + templateopts
111 ] + templateopts
112
112
113 diffopts = [
113 diffopts = [
114 ('a', 'text', None, _('treat all files as text')),
114 ('a', 'text', None, _('treat all files as text')),
115 ('g', 'git', None, _('use git extended diff format')),
115 ('g', 'git', None, _('use git extended diff format')),
116 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
116 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
117 ('', 'nodates', None, _('omit dates from diff headers'))
117 ('', 'nodates', None, _('omit dates from diff headers'))
118 ]
118 ]
119
119
120 diffwsopts = [
120 diffwsopts = [
121 ('w', 'ignore-all-space', None,
121 ('w', 'ignore-all-space', None,
122 _('ignore white space when comparing lines')),
122 _('ignore white space when comparing lines')),
123 ('b', 'ignore-space-change', None,
123 ('b', 'ignore-space-change', None,
124 _('ignore changes in the amount of white space')),
124 _('ignore changes in the amount of white space')),
125 ('B', 'ignore-blank-lines', None,
125 ('B', 'ignore-blank-lines', None,
126 _('ignore changes whose lines are all blank')),
126 _('ignore changes whose lines are all blank')),
127 ('Z', 'ignore-space-at-eol', None,
127 ('Z', 'ignore-space-at-eol', None,
128 _('ignore changes in whitespace at EOL')),
128 _('ignore changes in whitespace at EOL')),
129 ]
129 ]
130
130
131 diffopts2 = [
131 diffopts2 = [
132 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
132 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
133 ('p', 'show-function', None, _('show which function each change is in')),
133 ('p', 'show-function', None, _('show which function each change is in')),
134 ('', 'reverse', None, _('produce a diff that undoes the changes')),
134 ('', 'reverse', None, _('produce a diff that undoes the changes')),
135 ] + diffwsopts + [
135 ] + diffwsopts + [
136 ('U', 'unified', '',
136 ('U', 'unified', '',
137 _('number of lines of context to show'), _('NUM')),
137 _('number of lines of context to show'), _('NUM')),
138 ('', 'stat', None, _('output diffstat-style summary of changes')),
138 ('', 'stat', None, _('output diffstat-style summary of changes')),
139 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
139 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
140 ]
140 ]
141
141
142 mergetoolopts = [
142 mergetoolopts = [
143 ('t', 'tool', '', _('specify merge tool')),
143 ('t', 'tool', '', _('specify merge tool')),
144 ]
144 ]
145
145
146 similarityopts = [
146 similarityopts = [
147 ('s', 'similarity', '',
147 ('s', 'similarity', '',
148 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
148 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
149 ]
149 ]
150
150
151 subrepoopts = [
151 subrepoopts = [
152 ('S', 'subrepos', None,
152 ('S', 'subrepos', None,
153 _('recurse into subrepositories'))
153 _('recurse into subrepositories'))
154 ]
154 ]
155
155
156 debugrevlogopts = [
156 debugrevlogopts = [
157 ('c', 'changelog', False, _('open changelog')),
157 ('c', 'changelog', False, _('open changelog')),
158 ('m', 'manifest', False, _('open manifest')),
158 ('m', 'manifest', False, _('open manifest')),
159 ('', 'dir', '', _('open directory manifest')),
159 ('', 'dir', '', _('open directory manifest')),
160 ]
160 ]
161
161
162 # special string such that everything below this line will be ingored in the
162 # special string such that everything below this line will be ingored in the
163 # editor text
163 # editor text
164 _linebelow = "^HG: ------------------------ >8 ------------------------$"
164 _linebelow = "^HG: ------------------------ >8 ------------------------$"
165
165
166 def ishunk(x):
166 def ishunk(x):
167 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
167 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
168 return isinstance(x, hunkclasses)
168 return isinstance(x, hunkclasses)
169
169
170 def newandmodified(chunks, originalchunks):
170 def newandmodified(chunks, originalchunks):
171 newlyaddedandmodifiedfiles = set()
171 newlyaddedandmodifiedfiles = set()
172 for chunk in chunks:
172 for chunk in chunks:
173 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
173 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
174 originalchunks:
174 originalchunks:
175 newlyaddedandmodifiedfiles.add(chunk.header.filename())
175 newlyaddedandmodifiedfiles.add(chunk.header.filename())
176 return newlyaddedandmodifiedfiles
176 return newlyaddedandmodifiedfiles
177
177
178 def parsealiases(cmd):
178 def parsealiases(cmd):
179 return cmd.lstrip("^").split("|")
179 return cmd.lstrip("^").split("|")
180
180
181 def setupwrapcolorwrite(ui):
181 def setupwrapcolorwrite(ui):
182 # wrap ui.write so diff output can be labeled/colorized
182 # wrap ui.write so diff output can be labeled/colorized
183 def wrapwrite(orig, *args, **kw):
183 def wrapwrite(orig, *args, **kw):
184 label = kw.pop(r'label', '')
184 label = kw.pop(r'label', '')
185 for chunk, l in patch.difflabel(lambda: args):
185 for chunk, l in patch.difflabel(lambda: args):
186 orig(chunk, label=label + l)
186 orig(chunk, label=label + l)
187
187
188 oldwrite = ui.write
188 oldwrite = ui.write
189 def wrap(*args, **kwargs):
189 def wrap(*args, **kwargs):
190 return wrapwrite(oldwrite, *args, **kwargs)
190 return wrapwrite(oldwrite, *args, **kwargs)
191 setattr(ui, 'write', wrap)
191 setattr(ui, 'write', wrap)
192 return oldwrite
192 return oldwrite
193
193
194 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
194 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
195 if usecurses:
195 if usecurses:
196 if testfile:
196 if testfile:
197 recordfn = crecordmod.testdecorator(testfile,
197 recordfn = crecordmod.testdecorator(testfile,
198 crecordmod.testchunkselector)
198 crecordmod.testchunkselector)
199 else:
199 else:
200 recordfn = crecordmod.chunkselector
200 recordfn = crecordmod.chunkselector
201
201
202 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
202 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
203
203
204 else:
204 else:
205 return patch.filterpatch(ui, originalhunks, operation)
205 return patch.filterpatch(ui, originalhunks, operation)
206
206
207 def recordfilter(ui, originalhunks, operation=None):
207 def recordfilter(ui, originalhunks, operation=None):
208 """ Prompts the user to filter the originalhunks and return a list of
208 """ Prompts the user to filter the originalhunks and return a list of
209 selected hunks.
209 selected hunks.
210 *operation* is used for to build ui messages to indicate the user what
210 *operation* is used for to build ui messages to indicate the user what
211 kind of filtering they are doing: reverting, committing, shelving, etc.
211 kind of filtering they are doing: reverting, committing, shelving, etc.
212 (see patch.filterpatch).
212 (see patch.filterpatch).
213 """
213 """
214 usecurses = crecordmod.checkcurses(ui)
214 usecurses = crecordmod.checkcurses(ui)
215 testfile = ui.config('experimental', 'crecordtest')
215 testfile = ui.config('experimental', 'crecordtest')
216 oldwrite = setupwrapcolorwrite(ui)
216 oldwrite = setupwrapcolorwrite(ui)
217 try:
217 try:
218 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
218 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
219 testfile, operation)
219 testfile, operation)
220 finally:
220 finally:
221 ui.write = oldwrite
221 ui.write = oldwrite
222 return newchunks, newopts
222 return newchunks, newopts
223
223
224 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
224 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
225 filterfn, *pats, **opts):
225 filterfn, *pats, **opts):
226 from . import merge as mergemod
226 from . import merge as mergemod
227 opts = pycompat.byteskwargs(opts)
227 opts = pycompat.byteskwargs(opts)
228 if not ui.interactive():
228 if not ui.interactive():
229 if cmdsuggest:
229 if cmdsuggest:
230 msg = _('running non-interactively, use %s instead') % cmdsuggest
230 msg = _('running non-interactively, use %s instead') % cmdsuggest
231 else:
231 else:
232 msg = _('running non-interactively')
232 msg = _('running non-interactively')
233 raise error.Abort(msg)
233 raise error.Abort(msg)
234
234
235 # make sure username is set before going interactive
235 # make sure username is set before going interactive
236 if not opts.get('user'):
236 if not opts.get('user'):
237 ui.username() # raise exception, username not provided
237 ui.username() # raise exception, username not provided
238
238
239 def recordfunc(ui, repo, message, match, opts):
239 def recordfunc(ui, repo, message, match, opts):
240 """This is generic record driver.
240 """This is generic record driver.
241
241
242 Its job is to interactively filter local changes, and
242 Its job is to interactively filter local changes, and
243 accordingly prepare working directory into a state in which the
243 accordingly prepare working directory into a state in which the
244 job can be delegated to a non-interactive commit command such as
244 job can be delegated to a non-interactive commit command such as
245 'commit' or 'qrefresh'.
245 'commit' or 'qrefresh'.
246
246
247 After the actual job is done by non-interactive command, the
247 After the actual job is done by non-interactive command, the
248 working directory is restored to its original state.
248 working directory is restored to its original state.
249
249
250 In the end we'll record interesting changes, and everything else
250 In the end we'll record interesting changes, and everything else
251 will be left in place, so the user can continue working.
251 will be left in place, so the user can continue working.
252 """
252 """
253
253
254 checkunfinished(repo, commit=True)
254 checkunfinished(repo, commit=True)
255 wctx = repo[None]
255 wctx = repo[None]
256 merge = len(wctx.parents()) > 1
256 merge = len(wctx.parents()) > 1
257 if merge:
257 if merge:
258 raise error.Abort(_('cannot partially commit a merge '
258 raise error.Abort(_('cannot partially commit a merge '
259 '(use "hg commit" instead)'))
259 '(use "hg commit" instead)'))
260
260
261 def fail(f, msg):
261 def fail(f, msg):
262 raise error.Abort('%s: %s' % (f, msg))
262 raise error.Abort('%s: %s' % (f, msg))
263
263
264 force = opts.get('force')
264 force = opts.get('force')
265 if not force:
265 if not force:
266 vdirs = []
266 vdirs = []
267 match.explicitdir = vdirs.append
267 match.explicitdir = vdirs.append
268 match.bad = fail
268 match.bad = fail
269
269
270 status = repo.status(match=match)
270 status = repo.status(match=match)
271 if not force:
271 if not force:
272 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
272 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
273 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
273 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
274 diffopts.nodates = True
274 diffopts.nodates = True
275 diffopts.git = True
275 diffopts.git = True
276 diffopts.showfunc = True
276 diffopts.showfunc = True
277 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
277 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
278 originalchunks = patch.parsepatch(originaldiff)
278 originalchunks = patch.parsepatch(originaldiff)
279
279
280 # 1. filter patch, since we are intending to apply subset of it
280 # 1. filter patch, since we are intending to apply subset of it
281 try:
281 try:
282 chunks, newopts = filterfn(ui, originalchunks)
282 chunks, newopts = filterfn(ui, originalchunks)
283 except error.PatchError as err:
283 except error.PatchError as err:
284 raise error.Abort(_('error parsing patch: %s') % err)
284 raise error.Abort(_('error parsing patch: %s') % err)
285 opts.update(newopts)
285 opts.update(newopts)
286
286
287 # We need to keep a backup of files that have been newly added and
287 # We need to keep a backup of files that have been newly added and
288 # modified during the recording process because there is a previous
288 # modified during the recording process because there is a previous
289 # version without the edit in the workdir
289 # version without the edit in the workdir
290 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
290 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
291 contenders = set()
291 contenders = set()
292 for h in chunks:
292 for h in chunks:
293 try:
293 try:
294 contenders.update(set(h.files()))
294 contenders.update(set(h.files()))
295 except AttributeError:
295 except AttributeError:
296 pass
296 pass
297
297
298 changed = status.modified + status.added + status.removed
298 changed = status.modified + status.added + status.removed
299 newfiles = [f for f in changed if f in contenders]
299 newfiles = [f for f in changed if f in contenders]
300 if not newfiles:
300 if not newfiles:
301 ui.status(_('no changes to record\n'))
301 ui.status(_('no changes to record\n'))
302 return 0
302 return 0
303
303
304 modified = set(status.modified)
304 modified = set(status.modified)
305
305
306 # 2. backup changed files, so we can restore them in the end
306 # 2. backup changed files, so we can restore them in the end
307
307
308 if backupall:
308 if backupall:
309 tobackup = changed
309 tobackup = changed
310 else:
310 else:
311 tobackup = [f for f in newfiles if f in modified or f in \
311 tobackup = [f for f in newfiles if f in modified or f in \
312 newlyaddedandmodifiedfiles]
312 newlyaddedandmodifiedfiles]
313 backups = {}
313 backups = {}
314 if tobackup:
314 if tobackup:
315 backupdir = repo.vfs.join('record-backups')
315 backupdir = repo.vfs.join('record-backups')
316 try:
316 try:
317 os.mkdir(backupdir)
317 os.mkdir(backupdir)
318 except OSError as err:
318 except OSError as err:
319 if err.errno != errno.EEXIST:
319 if err.errno != errno.EEXIST:
320 raise
320 raise
321 try:
321 try:
322 # backup continues
322 # backup continues
323 for f in tobackup:
323 for f in tobackup:
324 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
324 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
325 dir=backupdir)
325 dir=backupdir)
326 os.close(fd)
326 os.close(fd)
327 ui.debug('backup %r as %r\n' % (f, tmpname))
327 ui.debug('backup %r as %r\n' % (f, tmpname))
328 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
328 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
329 backups[f] = tmpname
329 backups[f] = tmpname
330
330
331 fp = stringio()
331 fp = stringio()
332 for c in chunks:
332 for c in chunks:
333 fname = c.filename()
333 fname = c.filename()
334 if fname in backups:
334 if fname in backups:
335 c.write(fp)
335 c.write(fp)
336 dopatch = fp.tell()
336 dopatch = fp.tell()
337 fp.seek(0)
337 fp.seek(0)
338
338
339 # 2.5 optionally review / modify patch in text editor
339 # 2.5 optionally review / modify patch in text editor
340 if opts.get('review', False):
340 if opts.get('review', False):
341 patchtext = (crecordmod.diffhelptext
341 patchtext = (crecordmod.diffhelptext
342 + crecordmod.patchhelptext
342 + crecordmod.patchhelptext
343 + fp.read())
343 + fp.read())
344 reviewedpatch = ui.edit(patchtext, "",
344 reviewedpatch = ui.edit(patchtext, "",
345 action="diff",
345 action="diff",
346 repopath=repo.path)
346 repopath=repo.path)
347 fp.truncate(0)
347 fp.truncate(0)
348 fp.write(reviewedpatch)
348 fp.write(reviewedpatch)
349 fp.seek(0)
349 fp.seek(0)
350
350
351 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
351 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
352 # 3a. apply filtered patch to clean repo (clean)
352 # 3a. apply filtered patch to clean repo (clean)
353 if backups:
353 if backups:
354 # Equivalent to hg.revert
354 # Equivalent to hg.revert
355 m = scmutil.matchfiles(repo, backups.keys())
355 m = scmutil.matchfiles(repo, backups.keys())
356 mergemod.update(repo, repo.dirstate.p1(),
356 mergemod.update(repo, repo.dirstate.p1(),
357 False, True, matcher=m)
357 False, True, matcher=m)
358
358
359 # 3b. (apply)
359 # 3b. (apply)
360 if dopatch:
360 if dopatch:
361 try:
361 try:
362 ui.debug('applying patch\n')
362 ui.debug('applying patch\n')
363 ui.debug(fp.getvalue())
363 ui.debug(fp.getvalue())
364 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
364 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
365 except error.PatchError as err:
365 except error.PatchError as err:
366 raise error.Abort(str(err))
366 raise error.Abort(str(err))
367 del fp
367 del fp
368
368
369 # 4. We prepared working directory according to filtered
369 # 4. We prepared working directory according to filtered
370 # patch. Now is the time to delegate the job to
370 # patch. Now is the time to delegate the job to
371 # commit/qrefresh or the like!
371 # commit/qrefresh or the like!
372
372
373 # Make all of the pathnames absolute.
373 # Make all of the pathnames absolute.
374 newfiles = [repo.wjoin(nf) for nf in newfiles]
374 newfiles = [repo.wjoin(nf) for nf in newfiles]
375 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
375 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
376 finally:
376 finally:
377 # 5. finally restore backed-up files
377 # 5. finally restore backed-up files
378 try:
378 try:
379 dirstate = repo.dirstate
379 dirstate = repo.dirstate
380 for realname, tmpname in backups.iteritems():
380 for realname, tmpname in backups.iteritems():
381 ui.debug('restoring %r to %r\n' % (tmpname, realname))
381 ui.debug('restoring %r to %r\n' % (tmpname, realname))
382
382
383 if dirstate[realname] == 'n':
383 if dirstate[realname] == 'n':
384 # without normallookup, restoring timestamp
384 # without normallookup, restoring timestamp
385 # may cause partially committed files
385 # may cause partially committed files
386 # to be treated as unmodified
386 # to be treated as unmodified
387 dirstate.normallookup(realname)
387 dirstate.normallookup(realname)
388
388
389 # copystat=True here and above are a hack to trick any
389 # copystat=True here and above are a hack to trick any
390 # editors that have f open that we haven't modified them.
390 # editors that have f open that we haven't modified them.
391 #
391 #
392 # Also note that this racy as an editor could notice the
392 # Also note that this racy as an editor could notice the
393 # file's mtime before we've finished writing it.
393 # file's mtime before we've finished writing it.
394 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
394 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
395 os.unlink(tmpname)
395 os.unlink(tmpname)
396 if tobackup:
396 if tobackup:
397 os.rmdir(backupdir)
397 os.rmdir(backupdir)
398 except OSError:
398 except OSError:
399 pass
399 pass
400
400
401 def recordinwlock(ui, repo, message, match, opts):
401 def recordinwlock(ui, repo, message, match, opts):
402 with repo.wlock():
402 with repo.wlock():
403 return recordfunc(ui, repo, message, match, opts)
403 return recordfunc(ui, repo, message, match, opts)
404
404
405 return commit(ui, repo, recordinwlock, pats, opts)
405 return commit(ui, repo, recordinwlock, pats, opts)
406
406
407 class dirnode(object):
407 class dirnode(object):
408 """
408 """
409 Represent a directory in user working copy with information required for
409 Represent a directory in user working copy with information required for
410 the purpose of tersing its status.
410 the purpose of tersing its status.
411
411
412 path is the path to the directory
412 path is the path to the directory
413
413
414 statuses is a set of statuses of all files in this directory (this includes
414 statuses is a set of statuses of all files in this directory (this includes
415 all the files in all the subdirectories too)
415 all the files in all the subdirectories too)
416
416
417 files is a list of files which are direct child of this directory
417 files is a list of files which are direct child of this directory
418
418
419 subdirs is a dictionary of sub-directory name as the key and it's own
419 subdirs is a dictionary of sub-directory name as the key and it's own
420 dirnode object as the value
420 dirnode object as the value
421 """
421 """
422
422
423 def __init__(self, dirpath):
423 def __init__(self, dirpath):
424 self.path = dirpath
424 self.path = dirpath
425 self.statuses = set([])
425 self.statuses = set([])
426 self.files = []
426 self.files = []
427 self.subdirs = {}
427 self.subdirs = {}
428
428
429 def _addfileindir(self, filename, status):
429 def _addfileindir(self, filename, status):
430 """Add a file in this directory as a direct child."""
430 """Add a file in this directory as a direct child."""
431 self.files.append((filename, status))
431 self.files.append((filename, status))
432
432
433 def addfile(self, filename, status):
433 def addfile(self, filename, status):
434 """
434 """
435 Add a file to this directory or to its direct parent directory.
435 Add a file to this directory or to its direct parent directory.
436
436
437 If the file is not direct child of this directory, we traverse to the
437 If the file is not direct child of this directory, we traverse to the
438 directory of which this file is a direct child of and add the file
438 directory of which this file is a direct child of and add the file
439 there.
439 there.
440 """
440 """
441
441
442 # the filename contains a path separator, it means it's not the direct
442 # the filename contains a path separator, it means it's not the direct
443 # child of this directory
443 # child of this directory
444 if '/' in filename:
444 if '/' in filename:
445 subdir, filep = filename.split('/', 1)
445 subdir, filep = filename.split('/', 1)
446
446
447 # does the dirnode object for subdir exists
447 # does the dirnode object for subdir exists
448 if subdir not in self.subdirs:
448 if subdir not in self.subdirs:
449 subdirpath = os.path.join(self.path, subdir)
449 subdirpath = os.path.join(self.path, subdir)
450 self.subdirs[subdir] = dirnode(subdirpath)
450 self.subdirs[subdir] = dirnode(subdirpath)
451
451
452 # try adding the file in subdir
452 # try adding the file in subdir
453 self.subdirs[subdir].addfile(filep, status)
453 self.subdirs[subdir].addfile(filep, status)
454
454
455 else:
455 else:
456 self._addfileindir(filename, status)
456 self._addfileindir(filename, status)
457
457
458 if status not in self.statuses:
458 if status not in self.statuses:
459 self.statuses.add(status)
459 self.statuses.add(status)
460
460
461 def iterfilepaths(self):
461 def iterfilepaths(self):
462 """Yield (status, path) for files directly under this directory."""
462 """Yield (status, path) for files directly under this directory."""
463 for f, st in self.files:
463 for f, st in self.files:
464 yield st, os.path.join(self.path, f)
464 yield st, os.path.join(self.path, f)
465
465
466 def tersewalk(self, terseargs):
466 def tersewalk(self, terseargs):
467 """
467 """
468 Yield (status, path) obtained by processing the status of this
468 Yield (status, path) obtained by processing the status of this
469 dirnode.
469 dirnode.
470
470
471 terseargs is the string of arguments passed by the user with `--terse`
471 terseargs is the string of arguments passed by the user with `--terse`
472 flag.
472 flag.
473
473
474 Following are the cases which can happen:
474 Following are the cases which can happen:
475
475
476 1) All the files in the directory (including all the files in its
476 1) All the files in the directory (including all the files in its
477 subdirectories) share the same status and the user has asked us to terse
477 subdirectories) share the same status and the user has asked us to terse
478 that status. -> yield (status, dirpath)
478 that status. -> yield (status, dirpath)
479
479
480 2) Otherwise, we do following:
480 2) Otherwise, we do following:
481
481
482 a) Yield (status, filepath) for all the files which are in this
482 a) Yield (status, filepath) for all the files which are in this
483 directory (only the ones in this directory, not the subdirs)
483 directory (only the ones in this directory, not the subdirs)
484
484
485 b) Recurse the function on all the subdirectories of this
485 b) Recurse the function on all the subdirectories of this
486 directory
486 directory
487 """
487 """
488
488
489 if len(self.statuses) == 1:
489 if len(self.statuses) == 1:
490 onlyst = self.statuses.pop()
490 onlyst = self.statuses.pop()
491
491
492 # Making sure we terse only when the status abbreviation is
492 # Making sure we terse only when the status abbreviation is
493 # passed as terse argument
493 # passed as terse argument
494 if onlyst in terseargs:
494 if onlyst in terseargs:
495 yield onlyst, self.path + pycompat.ossep
495 yield onlyst, self.path + pycompat.ossep
496 return
496 return
497
497
498 # add the files to status list
498 # add the files to status list
499 for st, fpath in self.iterfilepaths():
499 for st, fpath in self.iterfilepaths():
500 yield st, fpath
500 yield st, fpath
501
501
502 #recurse on the subdirs
502 #recurse on the subdirs
503 for dirobj in self.subdirs.values():
503 for dirobj in self.subdirs.values():
504 for st, fpath in dirobj.tersewalk(terseargs):
504 for st, fpath in dirobj.tersewalk(terseargs):
505 yield st, fpath
505 yield st, fpath
506
506
507 def tersedir(statuslist, terseargs):
507 def tersedir(statuslist, terseargs):
508 """
508 """
509 Terse the status if all the files in a directory shares the same status.
509 Terse the status if all the files in a directory shares the same status.
510
510
511 statuslist is scmutil.status() object which contains a list of files for
511 statuslist is scmutil.status() object which contains a list of files for
512 each status.
512 each status.
513 terseargs is string which is passed by the user as the argument to `--terse`
513 terseargs is string which is passed by the user as the argument to `--terse`
514 flag.
514 flag.
515
515
516 The function makes a tree of objects of dirnode class, and at each node it
516 The function makes a tree of objects of dirnode class, and at each node it
517 stores the information required to know whether we can terse a certain
517 stores the information required to know whether we can terse a certain
518 directory or not.
518 directory or not.
519 """
519 """
520 # the order matters here as that is used to produce final list
520 # the order matters here as that is used to produce final list
521 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
521 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
522
522
523 # checking the argument validity
523 # checking the argument validity
524 for s in pycompat.bytestr(terseargs):
524 for s in pycompat.bytestr(terseargs):
525 if s not in allst:
525 if s not in allst:
526 raise error.Abort(_("'%s' not recognized") % s)
526 raise error.Abort(_("'%s' not recognized") % s)
527
527
528 # creating a dirnode object for the root of the repo
528 # creating a dirnode object for the root of the repo
529 rootobj = dirnode('')
529 rootobj = dirnode('')
530 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
530 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
531 'ignored', 'removed')
531 'ignored', 'removed')
532
532
533 tersedict = {}
533 tersedict = {}
534 for attrname in pstatus:
534 for attrname in pstatus:
535 statuschar = attrname[0:1]
535 statuschar = attrname[0:1]
536 for f in getattr(statuslist, attrname):
536 for f in getattr(statuslist, attrname):
537 rootobj.addfile(f, statuschar)
537 rootobj.addfile(f, statuschar)
538 tersedict[statuschar] = []
538 tersedict[statuschar] = []
539
539
540 # we won't be tersing the root dir, so add files in it
540 # we won't be tersing the root dir, so add files in it
541 for st, fpath in rootobj.iterfilepaths():
541 for st, fpath in rootobj.iterfilepaths():
542 tersedict[st].append(fpath)
542 tersedict[st].append(fpath)
543
543
544 # process each sub-directory and build tersedict
544 # process each sub-directory and build tersedict
545 for subdir in rootobj.subdirs.values():
545 for subdir in rootobj.subdirs.values():
546 for st, f in subdir.tersewalk(terseargs):
546 for st, f in subdir.tersewalk(terseargs):
547 tersedict[st].append(f)
547 tersedict[st].append(f)
548
548
549 tersedlist = []
549 tersedlist = []
550 for st in allst:
550 for st in allst:
551 tersedict[st].sort()
551 tersedict[st].sort()
552 tersedlist.append(tersedict[st])
552 tersedlist.append(tersedict[st])
553
553
554 return tersedlist
554 return tersedlist
555
555
556 def _commentlines(raw):
556 def _commentlines(raw):
557 '''Surround lineswith a comment char and a new line'''
557 '''Surround lineswith a comment char and a new line'''
558 lines = raw.splitlines()
558 lines = raw.splitlines()
559 commentedlines = ['# %s' % line for line in lines]
559 commentedlines = ['# %s' % line for line in lines]
560 return '\n'.join(commentedlines) + '\n'
560 return '\n'.join(commentedlines) + '\n'
561
561
562 def _conflictsmsg(repo):
562 def _conflictsmsg(repo):
563 # avoid merge cycle
563 # avoid merge cycle
564 from . import merge as mergemod
564 from . import merge as mergemod
565 mergestate = mergemod.mergestate.read(repo)
565 mergestate = mergemod.mergestate.read(repo)
566 if not mergestate.active():
566 if not mergestate.active():
567 return
567 return
568
568
569 m = scmutil.match(repo[None])
569 m = scmutil.match(repo[None])
570 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
570 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
571 if unresolvedlist:
571 if unresolvedlist:
572 mergeliststr = '\n'.join(
572 mergeliststr = '\n'.join(
573 [' %s' % util.pathto(repo.root, pycompat.getcwd(), path)
573 [' %s' % util.pathto(repo.root, pycompat.getcwd(), path)
574 for path in unresolvedlist])
574 for path in unresolvedlist])
575 msg = _('''Unresolved merge conflicts:
575 msg = _('''Unresolved merge conflicts:
576
576
577 %s
577 %s
578
578
579 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
579 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
580 else:
580 else:
581 msg = _('No unresolved merge conflicts.')
581 msg = _('No unresolved merge conflicts.')
582
582
583 return _commentlines(msg)
583 return _commentlines(msg)
584
584
585 def _helpmessage(continuecmd, abortcmd):
585 def _helpmessage(continuecmd, abortcmd):
586 msg = _('To continue: %s\n'
586 msg = _('To continue: %s\n'
587 'To abort: %s') % (continuecmd, abortcmd)
587 'To abort: %s') % (continuecmd, abortcmd)
588 return _commentlines(msg)
588 return _commentlines(msg)
589
589
590 def _rebasemsg():
590 def _rebasemsg():
591 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
591 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
592
592
593 def _histeditmsg():
593 def _histeditmsg():
594 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
594 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
595
595
596 def _unshelvemsg():
596 def _unshelvemsg():
597 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
597 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
598
598
599 def _updatecleanmsg(dest=None):
599 def _updatecleanmsg(dest=None):
600 warning = _('warning: this will discard uncommitted changes')
600 warning = _('warning: this will discard uncommitted changes')
601 return 'hg update --clean %s (%s)' % (dest or '.', warning)
601 return 'hg update --clean %s (%s)' % (dest or '.', warning)
602
602
603 def _graftmsg():
603 def _graftmsg():
604 # tweakdefaults requires `update` to have a rev hence the `.`
604 # tweakdefaults requires `update` to have a rev hence the `.`
605 return _helpmessage('hg graft --continue', _updatecleanmsg())
605 return _helpmessage('hg graft --continue', _updatecleanmsg())
606
606
607 def _mergemsg():
607 def _mergemsg():
608 # tweakdefaults requires `update` to have a rev hence the `.`
608 # tweakdefaults requires `update` to have a rev hence the `.`
609 return _helpmessage('hg commit', _updatecleanmsg())
609 return _helpmessage('hg commit', _updatecleanmsg())
610
610
611 def _bisectmsg():
611 def _bisectmsg():
612 msg = _('To mark the changeset good: hg bisect --good\n'
612 msg = _('To mark the changeset good: hg bisect --good\n'
613 'To mark the changeset bad: hg bisect --bad\n'
613 'To mark the changeset bad: hg bisect --bad\n'
614 'To abort: hg bisect --reset\n')
614 'To abort: hg bisect --reset\n')
615 return _commentlines(msg)
615 return _commentlines(msg)
616
616
617 def fileexistspredicate(filename):
617 def fileexistspredicate(filename):
618 return lambda repo: repo.vfs.exists(filename)
618 return lambda repo: repo.vfs.exists(filename)
619
619
620 def _mergepredicate(repo):
620 def _mergepredicate(repo):
621 return len(repo[None].parents()) > 1
621 return len(repo[None].parents()) > 1
622
622
623 STATES = (
623 STATES = (
624 # (state, predicate to detect states, helpful message function)
624 # (state, predicate to detect states, helpful message function)
625 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
625 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
626 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
626 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
627 ('graft', fileexistspredicate('graftstate'), _graftmsg),
627 ('graft', fileexistspredicate('graftstate'), _graftmsg),
628 ('unshelve', fileexistspredicate('unshelverebasestate'), _unshelvemsg),
628 ('unshelve', fileexistspredicate('unshelverebasestate'), _unshelvemsg),
629 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
629 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
630 # The merge state is part of a list that will be iterated over.
630 # The merge state is part of a list that will be iterated over.
631 # They need to be last because some of the other unfinished states may also
631 # They need to be last because some of the other unfinished states may also
632 # be in a merge or update state (eg. rebase, histedit, graft, etc).
632 # be in a merge or update state (eg. rebase, histedit, graft, etc).
633 # We want those to have priority.
633 # We want those to have priority.
634 ('merge', _mergepredicate, _mergemsg),
634 ('merge', _mergepredicate, _mergemsg),
635 )
635 )
636
636
637 def _getrepostate(repo):
637 def _getrepostate(repo):
638 # experimental config: commands.status.skipstates
638 # experimental config: commands.status.skipstates
639 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
639 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
640 for state, statedetectionpredicate, msgfn in STATES:
640 for state, statedetectionpredicate, msgfn in STATES:
641 if state in skip:
641 if state in skip:
642 continue
642 continue
643 if statedetectionpredicate(repo):
643 if statedetectionpredicate(repo):
644 return (state, statedetectionpredicate, msgfn)
644 return (state, statedetectionpredicate, msgfn)
645
645
646 def morestatus(repo, fm):
646 def morestatus(repo, fm):
647 statetuple = _getrepostate(repo)
647 statetuple = _getrepostate(repo)
648 label = 'status.morestatus'
648 label = 'status.morestatus'
649 if statetuple:
649 if statetuple:
650 fm.startitem()
650 fm.startitem()
651 state, statedetectionpredicate, helpfulmsg = statetuple
651 state, statedetectionpredicate, helpfulmsg = statetuple
652 statemsg = _('The repository is in an unfinished *%s* state.') % state
652 statemsg = _('The repository is in an unfinished *%s* state.') % state
653 fm.write('statemsg', '%s\n', _commentlines(statemsg), label=label)
653 fm.write('statemsg', '%s\n', _commentlines(statemsg), label=label)
654 conmsg = _conflictsmsg(repo)
654 conmsg = _conflictsmsg(repo)
655 if conmsg:
655 if conmsg:
656 fm.write('conflictsmsg', '%s\n', conmsg, label=label)
656 fm.write('conflictsmsg', '%s\n', conmsg, label=label)
657 if helpfulmsg:
657 if helpfulmsg:
658 helpmsg = helpfulmsg()
658 helpmsg = helpfulmsg()
659 fm.write('helpmsg', '%s\n', helpmsg, label=label)
659 fm.write('helpmsg', '%s\n', helpmsg, label=label)
660
660
661 def findpossible(cmd, table, strict=False):
661 def findpossible(cmd, table, strict=False):
662 """
662 """
663 Return cmd -> (aliases, command table entry)
663 Return cmd -> (aliases, command table entry)
664 for each matching command.
664 for each matching command.
665 Return debug commands (or their aliases) only if no normal command matches.
665 Return debug commands (or their aliases) only if no normal command matches.
666 """
666 """
667 choice = {}
667 choice = {}
668 debugchoice = {}
668 debugchoice = {}
669
669
670 if cmd in table:
670 if cmd in table:
671 # short-circuit exact matches, "log" alias beats "^log|history"
671 # short-circuit exact matches, "log" alias beats "^log|history"
672 keys = [cmd]
672 keys = [cmd]
673 else:
673 else:
674 keys = table.keys()
674 keys = table.keys()
675
675
676 allcmds = []
676 allcmds = []
677 for e in keys:
677 for e in keys:
678 aliases = parsealiases(e)
678 aliases = parsealiases(e)
679 allcmds.extend(aliases)
679 allcmds.extend(aliases)
680 found = None
680 found = None
681 if cmd in aliases:
681 if cmd in aliases:
682 found = cmd
682 found = cmd
683 elif not strict:
683 elif not strict:
684 for a in aliases:
684 for a in aliases:
685 if a.startswith(cmd):
685 if a.startswith(cmd):
686 found = a
686 found = a
687 break
687 break
688 if found is not None:
688 if found is not None:
689 if aliases[0].startswith("debug") or found.startswith("debug"):
689 if aliases[0].startswith("debug") or found.startswith("debug"):
690 debugchoice[found] = (aliases, table[e])
690 debugchoice[found] = (aliases, table[e])
691 else:
691 else:
692 choice[found] = (aliases, table[e])
692 choice[found] = (aliases, table[e])
693
693
694 if not choice and debugchoice:
694 if not choice and debugchoice:
695 choice = debugchoice
695 choice = debugchoice
696
696
697 return choice, allcmds
697 return choice, allcmds
698
698
699 def findcmd(cmd, table, strict=True):
699 def findcmd(cmd, table, strict=True):
700 """Return (aliases, command table entry) for command string."""
700 """Return (aliases, command table entry) for command string."""
701 choice, allcmds = findpossible(cmd, table, strict)
701 choice, allcmds = findpossible(cmd, table, strict)
702
702
703 if cmd in choice:
703 if cmd in choice:
704 return choice[cmd]
704 return choice[cmd]
705
705
706 if len(choice) > 1:
706 if len(choice) > 1:
707 clist = sorted(choice)
707 clist = sorted(choice)
708 raise error.AmbiguousCommand(cmd, clist)
708 raise error.AmbiguousCommand(cmd, clist)
709
709
710 if choice:
710 if choice:
711 return list(choice.values())[0]
711 return list(choice.values())[0]
712
712
713 raise error.UnknownCommand(cmd, allcmds)
713 raise error.UnknownCommand(cmd, allcmds)
714
714
715 def findrepo(p):
715 def findrepo(p):
716 while not os.path.isdir(os.path.join(p, ".hg")):
716 while not os.path.isdir(os.path.join(p, ".hg")):
717 oldp, p = p, os.path.dirname(p)
717 oldp, p = p, os.path.dirname(p)
718 if p == oldp:
718 if p == oldp:
719 return None
719 return None
720
720
721 return p
721 return p
722
722
723 def bailifchanged(repo, merge=True, hint=None):
723 def bailifchanged(repo, merge=True, hint=None):
724 """ enforce the precondition that working directory must be clean.
724 """ enforce the precondition that working directory must be clean.
725
725
726 'merge' can be set to false if a pending uncommitted merge should be
726 'merge' can be set to false if a pending uncommitted merge should be
727 ignored (such as when 'update --check' runs).
727 ignored (such as when 'update --check' runs).
728
728
729 'hint' is the usual hint given to Abort exception.
729 'hint' is the usual hint given to Abort exception.
730 """
730 """
731
731
732 if merge and repo.dirstate.p2() != nullid:
732 if merge and repo.dirstate.p2() != nullid:
733 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
733 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
734 modified, added, removed, deleted = repo.status()[:4]
734 modified, added, removed, deleted = repo.status()[:4]
735 if modified or added or removed or deleted:
735 if modified or added or removed or deleted:
736 raise error.Abort(_('uncommitted changes'), hint=hint)
736 raise error.Abort(_('uncommitted changes'), hint=hint)
737 ctx = repo[None]
737 ctx = repo[None]
738 for s in sorted(ctx.substate):
738 for s in sorted(ctx.substate):
739 ctx.sub(s).bailifchanged(hint=hint)
739 ctx.sub(s).bailifchanged(hint=hint)
740
740
741 def logmessage(ui, opts):
741 def logmessage(ui, opts):
742 """ get the log message according to -m and -l option """
742 """ get the log message according to -m and -l option """
743 message = opts.get('message')
743 message = opts.get('message')
744 logfile = opts.get('logfile')
744 logfile = opts.get('logfile')
745
745
746 if message and logfile:
746 if message and logfile:
747 raise error.Abort(_('options --message and --logfile are mutually '
747 raise error.Abort(_('options --message and --logfile are mutually '
748 'exclusive'))
748 'exclusive'))
749 if not message and logfile:
749 if not message and logfile:
750 try:
750 try:
751 if isstdiofilename(logfile):
751 if isstdiofilename(logfile):
752 message = ui.fin.read()
752 message = ui.fin.read()
753 else:
753 else:
754 message = '\n'.join(util.readfile(logfile).splitlines())
754 message = '\n'.join(util.readfile(logfile).splitlines())
755 except IOError as inst:
755 except IOError as inst:
756 raise error.Abort(_("can't read commit message '%s': %s") %
756 raise error.Abort(_("can't read commit message '%s': %s") %
757 (logfile, encoding.strtolocal(inst.strerror)))
757 (logfile, encoding.strtolocal(inst.strerror)))
758 return message
758 return message
759
759
760 def mergeeditform(ctxorbool, baseformname):
760 def mergeeditform(ctxorbool, baseformname):
761 """return appropriate editform name (referencing a committemplate)
761 """return appropriate editform name (referencing a committemplate)
762
762
763 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
763 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
764 merging is committed.
764 merging is committed.
765
765
766 This returns baseformname with '.merge' appended if it is a merge,
766 This returns baseformname with '.merge' appended if it is a merge,
767 otherwise '.normal' is appended.
767 otherwise '.normal' is appended.
768 """
768 """
769 if isinstance(ctxorbool, bool):
769 if isinstance(ctxorbool, bool):
770 if ctxorbool:
770 if ctxorbool:
771 return baseformname + ".merge"
771 return baseformname + ".merge"
772 elif 1 < len(ctxorbool.parents()):
772 elif 1 < len(ctxorbool.parents()):
773 return baseformname + ".merge"
773 return baseformname + ".merge"
774
774
775 return baseformname + ".normal"
775 return baseformname + ".normal"
776
776
777 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
777 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
778 editform='', **opts):
778 editform='', **opts):
779 """get appropriate commit message editor according to '--edit' option
779 """get appropriate commit message editor according to '--edit' option
780
780
781 'finishdesc' is a function to be called with edited commit message
781 'finishdesc' is a function to be called with edited commit message
782 (= 'description' of the new changeset) just after editing, but
782 (= 'description' of the new changeset) just after editing, but
783 before checking empty-ness. It should return actual text to be
783 before checking empty-ness. It should return actual text to be
784 stored into history. This allows to change description before
784 stored into history. This allows to change description before
785 storing.
785 storing.
786
786
787 'extramsg' is a extra message to be shown in the editor instead of
787 'extramsg' is a extra message to be shown in the editor instead of
788 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
788 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
789 is automatically added.
789 is automatically added.
790
790
791 'editform' is a dot-separated list of names, to distinguish
791 'editform' is a dot-separated list of names, to distinguish
792 the purpose of commit text editing.
792 the purpose of commit text editing.
793
793
794 'getcommiteditor' returns 'commitforceeditor' regardless of
794 'getcommiteditor' returns 'commitforceeditor' regardless of
795 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
795 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
796 they are specific for usage in MQ.
796 they are specific for usage in MQ.
797 """
797 """
798 if edit or finishdesc or extramsg:
798 if edit or finishdesc or extramsg:
799 return lambda r, c, s: commitforceeditor(r, c, s,
799 return lambda r, c, s: commitforceeditor(r, c, s,
800 finishdesc=finishdesc,
800 finishdesc=finishdesc,
801 extramsg=extramsg,
801 extramsg=extramsg,
802 editform=editform)
802 editform=editform)
803 elif editform:
803 elif editform:
804 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
804 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
805 else:
805 else:
806 return commiteditor
806 return commiteditor
807
807
808 def loglimit(opts):
808 def loglimit(opts):
809 """get the log limit according to option -l/--limit"""
809 """get the log limit according to option -l/--limit"""
810 limit = opts.get('limit')
810 limit = opts.get('limit')
811 if limit:
811 if limit:
812 try:
812 try:
813 limit = int(limit)
813 limit = int(limit)
814 except ValueError:
814 except ValueError:
815 raise error.Abort(_('limit must be a positive integer'))
815 raise error.Abort(_('limit must be a positive integer'))
816 if limit <= 0:
816 if limit <= 0:
817 raise error.Abort(_('limit must be positive'))
817 raise error.Abort(_('limit must be positive'))
818 else:
818 else:
819 limit = None
819 limit = None
820 return limit
820 return limit
821
821
822 def makefilename(repo, pat, node, desc=None,
822 def makefilename(repo, pat, node, desc=None,
823 total=None, seqno=None, revwidth=None, pathname=None):
823 total=None, seqno=None, revwidth=None, pathname=None):
824 node_expander = {
824 node_expander = {
825 'H': lambda: hex(node),
825 'H': lambda: hex(node),
826 'R': lambda: '%d' % repo.changelog.rev(node),
826 'R': lambda: '%d' % repo.changelog.rev(node),
827 'h': lambda: short(node),
827 'h': lambda: short(node),
828 'm': lambda: re.sub('[^\w]', '_', desc or '')
828 'm': lambda: re.sub('[^\w]', '_', desc or '')
829 }
829 }
830 expander = {
830 expander = {
831 '%': lambda: '%',
831 '%': lambda: '%',
832 'b': lambda: os.path.basename(repo.root),
832 'b': lambda: os.path.basename(repo.root),
833 }
833 }
834
834
835 try:
835 try:
836 if node:
836 if node:
837 expander.update(node_expander)
837 expander.update(node_expander)
838 if node:
838 if node:
839 expander['r'] = (lambda:
839 expander['r'] = (lambda:
840 ('%d' % repo.changelog.rev(node)).zfill(revwidth or 0))
840 ('%d' % repo.changelog.rev(node)).zfill(revwidth or 0))
841 if total is not None:
841 if total is not None:
842 expander['N'] = lambda: '%d' % total
842 expander['N'] = lambda: '%d' % total
843 if seqno is not None:
843 if seqno is not None:
844 expander['n'] = lambda: '%d' % seqno
844 expander['n'] = lambda: '%d' % seqno
845 if total is not None and seqno is not None:
845 if total is not None and seqno is not None:
846 expander['n'] = (lambda: ('%d' % seqno).zfill(len('%d' % total)))
846 expander['n'] = (lambda: ('%d' % seqno).zfill(len('%d' % total)))
847 if pathname is not None:
847 if pathname is not None:
848 expander['s'] = lambda: os.path.basename(pathname)
848 expander['s'] = lambda: os.path.basename(pathname)
849 expander['d'] = lambda: os.path.dirname(pathname) or '.'
849 expander['d'] = lambda: os.path.dirname(pathname) or '.'
850 expander['p'] = lambda: pathname
850 expander['p'] = lambda: pathname
851
851
852 newname = []
852 newname = []
853 patlen = len(pat)
853 patlen = len(pat)
854 i = 0
854 i = 0
855 while i < patlen:
855 while i < patlen:
856 c = pat[i:i + 1]
856 c = pat[i:i + 1]
857 if c == '%':
857 if c == '%':
858 i += 1
858 i += 1
859 c = pat[i:i + 1]
859 c = pat[i:i + 1]
860 c = expander[c]()
860 c = expander[c]()
861 newname.append(c)
861 newname.append(c)
862 i += 1
862 i += 1
863 return ''.join(newname)
863 return ''.join(newname)
864 except KeyError as inst:
864 except KeyError as inst:
865 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
865 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
866 inst.args[0])
866 inst.args[0])
867
867
868 def isstdiofilename(pat):
868 def isstdiofilename(pat):
869 """True if the given pat looks like a filename denoting stdin/stdout"""
869 """True if the given pat looks like a filename denoting stdin/stdout"""
870 return not pat or pat == '-'
870 return not pat or pat == '-'
871
871
872 class _unclosablefile(object):
872 class _unclosablefile(object):
873 def __init__(self, fp):
873 def __init__(self, fp):
874 self._fp = fp
874 self._fp = fp
875
875
876 def close(self):
876 def close(self):
877 pass
877 pass
878
878
879 def __iter__(self):
879 def __iter__(self):
880 return iter(self._fp)
880 return iter(self._fp)
881
881
882 def __getattr__(self, attr):
882 def __getattr__(self, attr):
883 return getattr(self._fp, attr)
883 return getattr(self._fp, attr)
884
884
885 def __enter__(self):
885 def __enter__(self):
886 return self
886 return self
887
887
888 def __exit__(self, exc_type, exc_value, exc_tb):
888 def __exit__(self, exc_type, exc_value, exc_tb):
889 pass
889 pass
890
890
891 def makefileobj(repo, pat, node=None, desc=None, total=None,
891 def makefileobj(repo, pat, node=None, desc=None, total=None,
892 seqno=None, revwidth=None, mode='wb', modemap=None,
892 seqno=None, revwidth=None, mode='wb', modemap=None,
893 pathname=None):
893 pathname=None):
894
894
895 writable = mode not in ('r', 'rb')
895 writable = mode not in ('r', 'rb')
896
896
897 if isstdiofilename(pat):
897 if isstdiofilename(pat):
898 if writable:
898 if writable:
899 fp = repo.ui.fout
899 fp = repo.ui.fout
900 else:
900 else:
901 fp = repo.ui.fin
901 fp = repo.ui.fin
902 return _unclosablefile(fp)
902 return _unclosablefile(fp)
903 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
903 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
904 if modemap is not None:
904 if modemap is not None:
905 mode = modemap.get(fn, mode)
905 mode = modemap.get(fn, mode)
906 if mode == 'wb':
906 if mode == 'wb':
907 modemap[fn] = 'ab'
907 modemap[fn] = 'ab'
908 return open(fn, mode)
908 return open(fn, mode)
909
909
910 def openrevlog(repo, cmd, file_, opts):
910 def openrevlog(repo, cmd, file_, opts):
911 """opens the changelog, manifest, a filelog or a given revlog"""
911 """opens the changelog, manifest, a filelog or a given revlog"""
912 cl = opts['changelog']
912 cl = opts['changelog']
913 mf = opts['manifest']
913 mf = opts['manifest']
914 dir = opts['dir']
914 dir = opts['dir']
915 msg = None
915 msg = None
916 if cl and mf:
916 if cl and mf:
917 msg = _('cannot specify --changelog and --manifest at the same time')
917 msg = _('cannot specify --changelog and --manifest at the same time')
918 elif cl and dir:
918 elif cl and dir:
919 msg = _('cannot specify --changelog and --dir at the same time')
919 msg = _('cannot specify --changelog and --dir at the same time')
920 elif cl or mf or dir:
920 elif cl or mf or dir:
921 if file_:
921 if file_:
922 msg = _('cannot specify filename with --changelog or --manifest')
922 msg = _('cannot specify filename with --changelog or --manifest')
923 elif not repo:
923 elif not repo:
924 msg = _('cannot specify --changelog or --manifest or --dir '
924 msg = _('cannot specify --changelog or --manifest or --dir '
925 'without a repository')
925 'without a repository')
926 if msg:
926 if msg:
927 raise error.Abort(msg)
927 raise error.Abort(msg)
928
928
929 r = None
929 r = None
930 if repo:
930 if repo:
931 if cl:
931 if cl:
932 r = repo.unfiltered().changelog
932 r = repo.unfiltered().changelog
933 elif dir:
933 elif dir:
934 if 'treemanifest' not in repo.requirements:
934 if 'treemanifest' not in repo.requirements:
935 raise error.Abort(_("--dir can only be used on repos with "
935 raise error.Abort(_("--dir can only be used on repos with "
936 "treemanifest enabled"))
936 "treemanifest enabled"))
937 dirlog = repo.manifestlog._revlog.dirlog(dir)
937 dirlog = repo.manifestlog._revlog.dirlog(dir)
938 if len(dirlog):
938 if len(dirlog):
939 r = dirlog
939 r = dirlog
940 elif mf:
940 elif mf:
941 r = repo.manifestlog._revlog
941 r = repo.manifestlog._revlog
942 elif file_:
942 elif file_:
943 filelog = repo.file(file_)
943 filelog = repo.file(file_)
944 if len(filelog):
944 if len(filelog):
945 r = filelog
945 r = filelog
946 if not r:
946 if not r:
947 if not file_:
947 if not file_:
948 raise error.CommandError(cmd, _('invalid arguments'))
948 raise error.CommandError(cmd, _('invalid arguments'))
949 if not os.path.isfile(file_):
949 if not os.path.isfile(file_):
950 raise error.Abort(_("revlog '%s' not found") % file_)
950 raise error.Abort(_("revlog '%s' not found") % file_)
951 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
951 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
952 file_[:-2] + ".i")
952 file_[:-2] + ".i")
953 return r
953 return r
954
954
955 def copy(ui, repo, pats, opts, rename=False):
955 def copy(ui, repo, pats, opts, rename=False):
956 # called with the repo lock held
956 # called with the repo lock held
957 #
957 #
958 # hgsep => pathname that uses "/" to separate directories
958 # hgsep => pathname that uses "/" to separate directories
959 # ossep => pathname that uses os.sep to separate directories
959 # ossep => pathname that uses os.sep to separate directories
960 cwd = repo.getcwd()
960 cwd = repo.getcwd()
961 targets = {}
961 targets = {}
962 after = opts.get("after")
962 after = opts.get("after")
963 dryrun = opts.get("dry_run")
963 dryrun = opts.get("dry_run")
964 wctx = repo[None]
964 wctx = repo[None]
965
965
966 def walkpat(pat):
966 def walkpat(pat):
967 srcs = []
967 srcs = []
968 if after:
968 if after:
969 badstates = '?'
969 badstates = '?'
970 else:
970 else:
971 badstates = '?r'
971 badstates = '?r'
972 m = scmutil.match(wctx, [pat], opts, globbed=True)
972 m = scmutil.match(wctx, [pat], opts, globbed=True)
973 for abs in wctx.walk(m):
973 for abs in wctx.walk(m):
974 state = repo.dirstate[abs]
974 state = repo.dirstate[abs]
975 rel = m.rel(abs)
975 rel = m.rel(abs)
976 exact = m.exact(abs)
976 exact = m.exact(abs)
977 if state in badstates:
977 if state in badstates:
978 if exact and state == '?':
978 if exact and state == '?':
979 ui.warn(_('%s: not copying - file is not managed\n') % rel)
979 ui.warn(_('%s: not copying - file is not managed\n') % rel)
980 if exact and state == 'r':
980 if exact and state == 'r':
981 ui.warn(_('%s: not copying - file has been marked for'
981 ui.warn(_('%s: not copying - file has been marked for'
982 ' remove\n') % rel)
982 ' remove\n') % rel)
983 continue
983 continue
984 # abs: hgsep
984 # abs: hgsep
985 # rel: ossep
985 # rel: ossep
986 srcs.append((abs, rel, exact))
986 srcs.append((abs, rel, exact))
987 return srcs
987 return srcs
988
988
989 # abssrc: hgsep
989 # abssrc: hgsep
990 # relsrc: ossep
990 # relsrc: ossep
991 # otarget: ossep
991 # otarget: ossep
992 def copyfile(abssrc, relsrc, otarget, exact):
992 def copyfile(abssrc, relsrc, otarget, exact):
993 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
993 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
994 if '/' in abstarget:
994 if '/' in abstarget:
995 # We cannot normalize abstarget itself, this would prevent
995 # We cannot normalize abstarget itself, this would prevent
996 # case only renames, like a => A.
996 # case only renames, like a => A.
997 abspath, absname = abstarget.rsplit('/', 1)
997 abspath, absname = abstarget.rsplit('/', 1)
998 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
998 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
999 reltarget = repo.pathto(abstarget, cwd)
999 reltarget = repo.pathto(abstarget, cwd)
1000 target = repo.wjoin(abstarget)
1000 target = repo.wjoin(abstarget)
1001 src = repo.wjoin(abssrc)
1001 src = repo.wjoin(abssrc)
1002 state = repo.dirstate[abstarget]
1002 state = repo.dirstate[abstarget]
1003
1003
1004 scmutil.checkportable(ui, abstarget)
1004 scmutil.checkportable(ui, abstarget)
1005
1005
1006 # check for collisions
1006 # check for collisions
1007 prevsrc = targets.get(abstarget)
1007 prevsrc = targets.get(abstarget)
1008 if prevsrc is not None:
1008 if prevsrc is not None:
1009 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1009 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1010 (reltarget, repo.pathto(abssrc, cwd),
1010 (reltarget, repo.pathto(abssrc, cwd),
1011 repo.pathto(prevsrc, cwd)))
1011 repo.pathto(prevsrc, cwd)))
1012 return
1012 return
1013
1013
1014 # check for overwrites
1014 # check for overwrites
1015 exists = os.path.lexists(target)
1015 exists = os.path.lexists(target)
1016 samefile = False
1016 samefile = False
1017 if exists and abssrc != abstarget:
1017 if exists and abssrc != abstarget:
1018 if (repo.dirstate.normalize(abssrc) ==
1018 if (repo.dirstate.normalize(abssrc) ==
1019 repo.dirstate.normalize(abstarget)):
1019 repo.dirstate.normalize(abstarget)):
1020 if not rename:
1020 if not rename:
1021 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1021 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1022 return
1022 return
1023 exists = False
1023 exists = False
1024 samefile = True
1024 samefile = True
1025
1025
1026 if not after and exists or after and state in 'mn':
1026 if not after and exists or after and state in 'mn':
1027 if not opts['force']:
1027 if not opts['force']:
1028 if state in 'mn':
1028 if state in 'mn':
1029 msg = _('%s: not overwriting - file already committed\n')
1029 msg = _('%s: not overwriting - file already committed\n')
1030 if after:
1030 if after:
1031 flags = '--after --force'
1031 flags = '--after --force'
1032 else:
1032 else:
1033 flags = '--force'
1033 flags = '--force'
1034 if rename:
1034 if rename:
1035 hint = _('(hg rename %s to replace the file by '
1035 hint = _('(hg rename %s to replace the file by '
1036 'recording a rename)\n') % flags
1036 'recording a rename)\n') % flags
1037 else:
1037 else:
1038 hint = _('(hg copy %s to replace the file by '
1038 hint = _('(hg copy %s to replace the file by '
1039 'recording a copy)\n') % flags
1039 'recording a copy)\n') % flags
1040 else:
1040 else:
1041 msg = _('%s: not overwriting - file exists\n')
1041 msg = _('%s: not overwriting - file exists\n')
1042 if rename:
1042 if rename:
1043 hint = _('(hg rename --after to record the rename)\n')
1043 hint = _('(hg rename --after to record the rename)\n')
1044 else:
1044 else:
1045 hint = _('(hg copy --after to record the copy)\n')
1045 hint = _('(hg copy --after to record the copy)\n')
1046 ui.warn(msg % reltarget)
1046 ui.warn(msg % reltarget)
1047 ui.warn(hint)
1047 ui.warn(hint)
1048 return
1048 return
1049
1049
1050 if after:
1050 if after:
1051 if not exists:
1051 if not exists:
1052 if rename:
1052 if rename:
1053 ui.warn(_('%s: not recording move - %s does not exist\n') %
1053 ui.warn(_('%s: not recording move - %s does not exist\n') %
1054 (relsrc, reltarget))
1054 (relsrc, reltarget))
1055 else:
1055 else:
1056 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1056 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1057 (relsrc, reltarget))
1057 (relsrc, reltarget))
1058 return
1058 return
1059 elif not dryrun:
1059 elif not dryrun:
1060 try:
1060 try:
1061 if exists:
1061 if exists:
1062 os.unlink(target)
1062 os.unlink(target)
1063 targetdir = os.path.dirname(target) or '.'
1063 targetdir = os.path.dirname(target) or '.'
1064 if not os.path.isdir(targetdir):
1064 if not os.path.isdir(targetdir):
1065 os.makedirs(targetdir)
1065 os.makedirs(targetdir)
1066 if samefile:
1066 if samefile:
1067 tmp = target + "~hgrename"
1067 tmp = target + "~hgrename"
1068 os.rename(src, tmp)
1068 os.rename(src, tmp)
1069 os.rename(tmp, target)
1069 os.rename(tmp, target)
1070 else:
1070 else:
1071 util.copyfile(src, target)
1071 util.copyfile(src, target)
1072 srcexists = True
1072 srcexists = True
1073 except IOError as inst:
1073 except IOError as inst:
1074 if inst.errno == errno.ENOENT:
1074 if inst.errno == errno.ENOENT:
1075 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1075 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1076 srcexists = False
1076 srcexists = False
1077 else:
1077 else:
1078 ui.warn(_('%s: cannot copy - %s\n') %
1078 ui.warn(_('%s: cannot copy - %s\n') %
1079 (relsrc, encoding.strtolocal(inst.strerror)))
1079 (relsrc, encoding.strtolocal(inst.strerror)))
1080 return True # report a failure
1080 return True # report a failure
1081
1081
1082 if ui.verbose or not exact:
1082 if ui.verbose or not exact:
1083 if rename:
1083 if rename:
1084 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1084 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1085 else:
1085 else:
1086 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1086 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1087
1087
1088 targets[abstarget] = abssrc
1088 targets[abstarget] = abssrc
1089
1089
1090 # fix up dirstate
1090 # fix up dirstate
1091 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1091 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1092 dryrun=dryrun, cwd=cwd)
1092 dryrun=dryrun, cwd=cwd)
1093 if rename and not dryrun:
1093 if rename and not dryrun:
1094 if not after and srcexists and not samefile:
1094 if not after and srcexists and not samefile:
1095 repo.wvfs.unlinkpath(abssrc)
1095 repo.wvfs.unlinkpath(abssrc)
1096 wctx.forget([abssrc])
1096 wctx.forget([abssrc])
1097
1097
1098 # pat: ossep
1098 # pat: ossep
1099 # dest ossep
1099 # dest ossep
1100 # srcs: list of (hgsep, hgsep, ossep, bool)
1100 # srcs: list of (hgsep, hgsep, ossep, bool)
1101 # return: function that takes hgsep and returns ossep
1101 # return: function that takes hgsep and returns ossep
1102 def targetpathfn(pat, dest, srcs):
1102 def targetpathfn(pat, dest, srcs):
1103 if os.path.isdir(pat):
1103 if os.path.isdir(pat):
1104 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1104 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1105 abspfx = util.localpath(abspfx)
1105 abspfx = util.localpath(abspfx)
1106 if destdirexists:
1106 if destdirexists:
1107 striplen = len(os.path.split(abspfx)[0])
1107 striplen = len(os.path.split(abspfx)[0])
1108 else:
1108 else:
1109 striplen = len(abspfx)
1109 striplen = len(abspfx)
1110 if striplen:
1110 if striplen:
1111 striplen += len(pycompat.ossep)
1111 striplen += len(pycompat.ossep)
1112 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1112 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1113 elif destdirexists:
1113 elif destdirexists:
1114 res = lambda p: os.path.join(dest,
1114 res = lambda p: os.path.join(dest,
1115 os.path.basename(util.localpath(p)))
1115 os.path.basename(util.localpath(p)))
1116 else:
1116 else:
1117 res = lambda p: dest
1117 res = lambda p: dest
1118 return res
1118 return res
1119
1119
1120 # pat: ossep
1120 # pat: ossep
1121 # dest ossep
1121 # dest ossep
1122 # srcs: list of (hgsep, hgsep, ossep, bool)
1122 # srcs: list of (hgsep, hgsep, ossep, bool)
1123 # return: function that takes hgsep and returns ossep
1123 # return: function that takes hgsep and returns ossep
1124 def targetpathafterfn(pat, dest, srcs):
1124 def targetpathafterfn(pat, dest, srcs):
1125 if matchmod.patkind(pat):
1125 if matchmod.patkind(pat):
1126 # a mercurial pattern
1126 # a mercurial pattern
1127 res = lambda p: os.path.join(dest,
1127 res = lambda p: os.path.join(dest,
1128 os.path.basename(util.localpath(p)))
1128 os.path.basename(util.localpath(p)))
1129 else:
1129 else:
1130 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1130 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1131 if len(abspfx) < len(srcs[0][0]):
1131 if len(abspfx) < len(srcs[0][0]):
1132 # A directory. Either the target path contains the last
1132 # A directory. Either the target path contains the last
1133 # component of the source path or it does not.
1133 # component of the source path or it does not.
1134 def evalpath(striplen):
1134 def evalpath(striplen):
1135 score = 0
1135 score = 0
1136 for s in srcs:
1136 for s in srcs:
1137 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1137 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1138 if os.path.lexists(t):
1138 if os.path.lexists(t):
1139 score += 1
1139 score += 1
1140 return score
1140 return score
1141
1141
1142 abspfx = util.localpath(abspfx)
1142 abspfx = util.localpath(abspfx)
1143 striplen = len(abspfx)
1143 striplen = len(abspfx)
1144 if striplen:
1144 if striplen:
1145 striplen += len(pycompat.ossep)
1145 striplen += len(pycompat.ossep)
1146 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1146 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1147 score = evalpath(striplen)
1147 score = evalpath(striplen)
1148 striplen1 = len(os.path.split(abspfx)[0])
1148 striplen1 = len(os.path.split(abspfx)[0])
1149 if striplen1:
1149 if striplen1:
1150 striplen1 += len(pycompat.ossep)
1150 striplen1 += len(pycompat.ossep)
1151 if evalpath(striplen1) > score:
1151 if evalpath(striplen1) > score:
1152 striplen = striplen1
1152 striplen = striplen1
1153 res = lambda p: os.path.join(dest,
1153 res = lambda p: os.path.join(dest,
1154 util.localpath(p)[striplen:])
1154 util.localpath(p)[striplen:])
1155 else:
1155 else:
1156 # a file
1156 # a file
1157 if destdirexists:
1157 if destdirexists:
1158 res = lambda p: os.path.join(dest,
1158 res = lambda p: os.path.join(dest,
1159 os.path.basename(util.localpath(p)))
1159 os.path.basename(util.localpath(p)))
1160 else:
1160 else:
1161 res = lambda p: dest
1161 res = lambda p: dest
1162 return res
1162 return res
1163
1163
1164 pats = scmutil.expandpats(pats)
1164 pats = scmutil.expandpats(pats)
1165 if not pats:
1165 if not pats:
1166 raise error.Abort(_('no source or destination specified'))
1166 raise error.Abort(_('no source or destination specified'))
1167 if len(pats) == 1:
1167 if len(pats) == 1:
1168 raise error.Abort(_('no destination specified'))
1168 raise error.Abort(_('no destination specified'))
1169 dest = pats.pop()
1169 dest = pats.pop()
1170 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1170 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1171 if not destdirexists:
1171 if not destdirexists:
1172 if len(pats) > 1 or matchmod.patkind(pats[0]):
1172 if len(pats) > 1 or matchmod.patkind(pats[0]):
1173 raise error.Abort(_('with multiple sources, destination must be an '
1173 raise error.Abort(_('with multiple sources, destination must be an '
1174 'existing directory'))
1174 'existing directory'))
1175 if util.endswithsep(dest):
1175 if util.endswithsep(dest):
1176 raise error.Abort(_('destination %s is not a directory') % dest)
1176 raise error.Abort(_('destination %s is not a directory') % dest)
1177
1177
1178 tfn = targetpathfn
1178 tfn = targetpathfn
1179 if after:
1179 if after:
1180 tfn = targetpathafterfn
1180 tfn = targetpathafterfn
1181 copylist = []
1181 copylist = []
1182 for pat in pats:
1182 for pat in pats:
1183 srcs = walkpat(pat)
1183 srcs = walkpat(pat)
1184 if not srcs:
1184 if not srcs:
1185 continue
1185 continue
1186 copylist.append((tfn(pat, dest, srcs), srcs))
1186 copylist.append((tfn(pat, dest, srcs), srcs))
1187 if not copylist:
1187 if not copylist:
1188 raise error.Abort(_('no files to copy'))
1188 raise error.Abort(_('no files to copy'))
1189
1189
1190 errors = 0
1190 errors = 0
1191 for targetpath, srcs in copylist:
1191 for targetpath, srcs in copylist:
1192 for abssrc, relsrc, exact in srcs:
1192 for abssrc, relsrc, exact in srcs:
1193 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1193 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1194 errors += 1
1194 errors += 1
1195
1195
1196 if errors:
1196 if errors:
1197 ui.warn(_('(consider using --after)\n'))
1197 ui.warn(_('(consider using --after)\n'))
1198
1198
1199 return errors != 0
1199 return errors != 0
1200
1200
1201 ## facility to let extension process additional data into an import patch
1201 ## facility to let extension process additional data into an import patch
1202 # list of identifier to be executed in order
1202 # list of identifier to be executed in order
1203 extrapreimport = [] # run before commit
1203 extrapreimport = [] # run before commit
1204 extrapostimport = [] # run after commit
1204 extrapostimport = [] # run after commit
1205 # mapping from identifier to actual import function
1205 # mapping from identifier to actual import function
1206 #
1206 #
1207 # 'preimport' are run before the commit is made and are provided the following
1207 # 'preimport' are run before the commit is made and are provided the following
1208 # arguments:
1208 # arguments:
1209 # - repo: the localrepository instance,
1209 # - repo: the localrepository instance,
1210 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1210 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1211 # - extra: the future extra dictionary of the changeset, please mutate it,
1211 # - extra: the future extra dictionary of the changeset, please mutate it,
1212 # - opts: the import options.
1212 # - opts: the import options.
1213 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1213 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1214 # mutation of in memory commit and more. Feel free to rework the code to get
1214 # mutation of in memory commit and more. Feel free to rework the code to get
1215 # there.
1215 # there.
1216 extrapreimportmap = {}
1216 extrapreimportmap = {}
1217 # 'postimport' are run after the commit is made and are provided the following
1217 # 'postimport' are run after the commit is made and are provided the following
1218 # argument:
1218 # argument:
1219 # - ctx: the changectx created by import.
1219 # - ctx: the changectx created by import.
1220 extrapostimportmap = {}
1220 extrapostimportmap = {}
1221
1221
1222 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
1222 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
1223 """Utility function used by commands.import to import a single patch
1223 """Utility function used by commands.import to import a single patch
1224
1224
1225 This function is explicitly defined here to help the evolve extension to
1225 This function is explicitly defined here to help the evolve extension to
1226 wrap this part of the import logic.
1226 wrap this part of the import logic.
1227
1227
1228 The API is currently a bit ugly because it a simple code translation from
1228 The API is currently a bit ugly because it a simple code translation from
1229 the import command. Feel free to make it better.
1229 the import command. Feel free to make it better.
1230
1230
1231 :hunk: a patch (as a binary string)
1231 :hunk: a patch (as a binary string)
1232 :parents: nodes that will be parent of the created commit
1232 :parents: nodes that will be parent of the created commit
1233 :opts: the full dict of option passed to the import command
1233 :opts: the full dict of option passed to the import command
1234 :msgs: list to save commit message to.
1234 :msgs: list to save commit message to.
1235 (used in case we need to save it when failing)
1235 (used in case we need to save it when failing)
1236 :updatefunc: a function that update a repo to a given node
1236 :updatefunc: a function that update a repo to a given node
1237 updatefunc(<repo>, <node>)
1237 updatefunc(<repo>, <node>)
1238 """
1238 """
1239 # avoid cycle context -> subrepo -> cmdutil
1239 # avoid cycle context -> subrepo -> cmdutil
1240 from . import context
1240 from . import context
1241 extractdata = patch.extract(ui, hunk)
1241 extractdata = patch.extract(ui, hunk)
1242 tmpname = extractdata.get('filename')
1242 tmpname = extractdata.get('filename')
1243 message = extractdata.get('message')
1243 message = extractdata.get('message')
1244 user = opts.get('user') or extractdata.get('user')
1244 user = opts.get('user') or extractdata.get('user')
1245 date = opts.get('date') or extractdata.get('date')
1245 date = opts.get('date') or extractdata.get('date')
1246 branch = extractdata.get('branch')
1246 branch = extractdata.get('branch')
1247 nodeid = extractdata.get('nodeid')
1247 nodeid = extractdata.get('nodeid')
1248 p1 = extractdata.get('p1')
1248 p1 = extractdata.get('p1')
1249 p2 = extractdata.get('p2')
1249 p2 = extractdata.get('p2')
1250
1250
1251 nocommit = opts.get('no_commit')
1251 nocommit = opts.get('no_commit')
1252 importbranch = opts.get('import_branch')
1252 importbranch = opts.get('import_branch')
1253 update = not opts.get('bypass')
1253 update = not opts.get('bypass')
1254 strip = opts["strip"]
1254 strip = opts["strip"]
1255 prefix = opts["prefix"]
1255 prefix = opts["prefix"]
1256 sim = float(opts.get('similarity') or 0)
1256 sim = float(opts.get('similarity') or 0)
1257 if not tmpname:
1257 if not tmpname:
1258 return (None, None, False)
1258 return (None, None, False)
1259
1259
1260 rejects = False
1260 rejects = False
1261
1261
1262 try:
1262 try:
1263 cmdline_message = logmessage(ui, opts)
1263 cmdline_message = logmessage(ui, opts)
1264 if cmdline_message:
1264 if cmdline_message:
1265 # pickup the cmdline msg
1265 # pickup the cmdline msg
1266 message = cmdline_message
1266 message = cmdline_message
1267 elif message:
1267 elif message:
1268 # pickup the patch msg
1268 # pickup the patch msg
1269 message = message.strip()
1269 message = message.strip()
1270 else:
1270 else:
1271 # launch the editor
1271 # launch the editor
1272 message = None
1272 message = None
1273 ui.debug('message:\n%s\n' % message)
1273 ui.debug('message:\n%s\n' % message)
1274
1274
1275 if len(parents) == 1:
1275 if len(parents) == 1:
1276 parents.append(repo[nullid])
1276 parents.append(repo[nullid])
1277 if opts.get('exact'):
1277 if opts.get('exact'):
1278 if not nodeid or not p1:
1278 if not nodeid or not p1:
1279 raise error.Abort(_('not a Mercurial patch'))
1279 raise error.Abort(_('not a Mercurial patch'))
1280 p1 = repo[p1]
1280 p1 = repo[p1]
1281 p2 = repo[p2 or nullid]
1281 p2 = repo[p2 or nullid]
1282 elif p2:
1282 elif p2:
1283 try:
1283 try:
1284 p1 = repo[p1]
1284 p1 = repo[p1]
1285 p2 = repo[p2]
1285 p2 = repo[p2]
1286 # Without any options, consider p2 only if the
1286 # Without any options, consider p2 only if the
1287 # patch is being applied on top of the recorded
1287 # patch is being applied on top of the recorded
1288 # first parent.
1288 # first parent.
1289 if p1 != parents[0]:
1289 if p1 != parents[0]:
1290 p1 = parents[0]
1290 p1 = parents[0]
1291 p2 = repo[nullid]
1291 p2 = repo[nullid]
1292 except error.RepoError:
1292 except error.RepoError:
1293 p1, p2 = parents
1293 p1, p2 = parents
1294 if p2.node() == nullid:
1294 if p2.node() == nullid:
1295 ui.warn(_("warning: import the patch as a normal revision\n"
1295 ui.warn(_("warning: import the patch as a normal revision\n"
1296 "(use --exact to import the patch as a merge)\n"))
1296 "(use --exact to import the patch as a merge)\n"))
1297 else:
1297 else:
1298 p1, p2 = parents
1298 p1, p2 = parents
1299
1299
1300 n = None
1300 n = None
1301 if update:
1301 if update:
1302 if p1 != parents[0]:
1302 if p1 != parents[0]:
1303 updatefunc(repo, p1.node())
1303 updatefunc(repo, p1.node())
1304 if p2 != parents[1]:
1304 if p2 != parents[1]:
1305 repo.setparents(p1.node(), p2.node())
1305 repo.setparents(p1.node(), p2.node())
1306
1306
1307 if opts.get('exact') or importbranch:
1307 if opts.get('exact') or importbranch:
1308 repo.dirstate.setbranch(branch or 'default')
1308 repo.dirstate.setbranch(branch or 'default')
1309
1309
1310 partial = opts.get('partial', False)
1310 partial = opts.get('partial', False)
1311 files = set()
1311 files = set()
1312 try:
1312 try:
1313 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1313 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1314 files=files, eolmode=None, similarity=sim / 100.0)
1314 files=files, eolmode=None, similarity=sim / 100.0)
1315 except error.PatchError as e:
1315 except error.PatchError as e:
1316 if not partial:
1316 if not partial:
1317 raise error.Abort(str(e))
1317 raise error.Abort(str(e))
1318 if partial:
1318 if partial:
1319 rejects = True
1319 rejects = True
1320
1320
1321 files = list(files)
1321 files = list(files)
1322 if nocommit:
1322 if nocommit:
1323 if message:
1323 if message:
1324 msgs.append(message)
1324 msgs.append(message)
1325 else:
1325 else:
1326 if opts.get('exact') or p2:
1326 if opts.get('exact') or p2:
1327 # If you got here, you either use --force and know what
1327 # If you got here, you either use --force and know what
1328 # you are doing or used --exact or a merge patch while
1328 # you are doing or used --exact or a merge patch while
1329 # being updated to its first parent.
1329 # being updated to its first parent.
1330 m = None
1330 m = None
1331 else:
1331 else:
1332 m = scmutil.matchfiles(repo, files or [])
1332 m = scmutil.matchfiles(repo, files or [])
1333 editform = mergeeditform(repo[None], 'import.normal')
1333 editform = mergeeditform(repo[None], 'import.normal')
1334 if opts.get('exact'):
1334 if opts.get('exact'):
1335 editor = None
1335 editor = None
1336 else:
1336 else:
1337 editor = getcommiteditor(editform=editform,
1337 editor = getcommiteditor(editform=editform,
1338 **pycompat.strkwargs(opts))
1338 **pycompat.strkwargs(opts))
1339 extra = {}
1339 extra = {}
1340 for idfunc in extrapreimport:
1340 for idfunc in extrapreimport:
1341 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1341 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1342 overrides = {}
1342 overrides = {}
1343 if partial:
1343 if partial:
1344 overrides[('ui', 'allowemptycommit')] = True
1344 overrides[('ui', 'allowemptycommit')] = True
1345 with repo.ui.configoverride(overrides, 'import'):
1345 with repo.ui.configoverride(overrides, 'import'):
1346 n = repo.commit(message, user,
1346 n = repo.commit(message, user,
1347 date, match=m,
1347 date, match=m,
1348 editor=editor, extra=extra)
1348 editor=editor, extra=extra)
1349 for idfunc in extrapostimport:
1349 for idfunc in extrapostimport:
1350 extrapostimportmap[idfunc](repo[n])
1350 extrapostimportmap[idfunc](repo[n])
1351 else:
1351 else:
1352 if opts.get('exact') or importbranch:
1352 if opts.get('exact') or importbranch:
1353 branch = branch or 'default'
1353 branch = branch or 'default'
1354 else:
1354 else:
1355 branch = p1.branch()
1355 branch = p1.branch()
1356 store = patch.filestore()
1356 store = patch.filestore()
1357 try:
1357 try:
1358 files = set()
1358 files = set()
1359 try:
1359 try:
1360 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1360 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1361 files, eolmode=None)
1361 files, eolmode=None)
1362 except error.PatchError as e:
1362 except error.PatchError as e:
1363 raise error.Abort(str(e))
1363 raise error.Abort(str(e))
1364 if opts.get('exact'):
1364 if opts.get('exact'):
1365 editor = None
1365 editor = None
1366 else:
1366 else:
1367 editor = getcommiteditor(editform='import.bypass')
1367 editor = getcommiteditor(editform='import.bypass')
1368 memctx = context.memctx(repo, (p1.node(), p2.node()),
1368 memctx = context.memctx(repo, (p1.node(), p2.node()),
1369 message,
1369 message,
1370 files=files,
1370 files=files,
1371 filectxfn=store,
1371 filectxfn=store,
1372 user=user,
1372 user=user,
1373 date=date,
1373 date=date,
1374 branch=branch,
1374 branch=branch,
1375 editor=editor)
1375 editor=editor)
1376 n = memctx.commit()
1376 n = memctx.commit()
1377 finally:
1377 finally:
1378 store.close()
1378 store.close()
1379 if opts.get('exact') and nocommit:
1379 if opts.get('exact') and nocommit:
1380 # --exact with --no-commit is still useful in that it does merge
1380 # --exact with --no-commit is still useful in that it does merge
1381 # and branch bits
1381 # and branch bits
1382 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1382 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1383 elif opts.get('exact') and hex(n) != nodeid:
1383 elif opts.get('exact') and hex(n) != nodeid:
1384 raise error.Abort(_('patch is damaged or loses information'))
1384 raise error.Abort(_('patch is damaged or loses information'))
1385 msg = _('applied to working directory')
1385 msg = _('applied to working directory')
1386 if n:
1386 if n:
1387 # i18n: refers to a short changeset id
1387 # i18n: refers to a short changeset id
1388 msg = _('created %s') % short(n)
1388 msg = _('created %s') % short(n)
1389 return (msg, n, rejects)
1389 return (msg, n, rejects)
1390 finally:
1390 finally:
1391 os.unlink(tmpname)
1391 os.unlink(tmpname)
1392
1392
1393 # facility to let extensions include additional data in an exported patch
1393 # facility to let extensions include additional data in an exported patch
1394 # list of identifiers to be executed in order
1394 # list of identifiers to be executed in order
1395 extraexport = []
1395 extraexport = []
1396 # mapping from identifier to actual export function
1396 # mapping from identifier to actual export function
1397 # function as to return a string to be added to the header or None
1397 # function as to return a string to be added to the header or None
1398 # it is given two arguments (sequencenumber, changectx)
1398 # it is given two arguments (sequencenumber, changectx)
1399 extraexportmap = {}
1399 extraexportmap = {}
1400
1400
1401 def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
1401 def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
1402 node = scmutil.binnode(ctx)
1402 node = scmutil.binnode(ctx)
1403 parents = [p.node() for p in ctx.parents() if p]
1403 parents = [p.node() for p in ctx.parents() if p]
1404 branch = ctx.branch()
1404 branch = ctx.branch()
1405 if switch_parent:
1405 if switch_parent:
1406 parents.reverse()
1406 parents.reverse()
1407
1407
1408 if parents:
1408 if parents:
1409 prev = parents[0]
1409 prev = parents[0]
1410 else:
1410 else:
1411 prev = nullid
1411 prev = nullid
1412
1412
1413 write("# HG changeset patch\n")
1413 write("# HG changeset patch\n")
1414 write("# User %s\n" % ctx.user())
1414 write("# User %s\n" % ctx.user())
1415 write("# Date %d %d\n" % ctx.date())
1415 write("# Date %d %d\n" % ctx.date())
1416 write("# %s\n" % util.datestr(ctx.date()))
1416 write("# %s\n" % util.datestr(ctx.date()))
1417 if branch and branch != 'default':
1417 if branch and branch != 'default':
1418 write("# Branch %s\n" % branch)
1418 write("# Branch %s\n" % branch)
1419 write("# Node ID %s\n" % hex(node))
1419 write("# Node ID %s\n" % hex(node))
1420 write("# Parent %s\n" % hex(prev))
1420 write("# Parent %s\n" % hex(prev))
1421 if len(parents) > 1:
1421 if len(parents) > 1:
1422 write("# Parent %s\n" % hex(parents[1]))
1422 write("# Parent %s\n" % hex(parents[1]))
1423
1423
1424 for headerid in extraexport:
1424 for headerid in extraexport:
1425 header = extraexportmap[headerid](seqno, ctx)
1425 header = extraexportmap[headerid](seqno, ctx)
1426 if header is not None:
1426 if header is not None:
1427 write('# %s\n' % header)
1427 write('# %s\n' % header)
1428 write(ctx.description().rstrip())
1428 write(ctx.description().rstrip())
1429 write("\n\n")
1429 write("\n\n")
1430
1430
1431 for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts):
1431 for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts):
1432 write(chunk, label=label)
1432 write(chunk, label=label)
1433
1433
1434 def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
1434 def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
1435 opts=None, match=None):
1435 opts=None, match=None):
1436 '''export changesets as hg patches
1436 '''export changesets as hg patches
1437
1437
1438 Args:
1438 Args:
1439 repo: The repository from which we're exporting revisions.
1439 repo: The repository from which we're exporting revisions.
1440 revs: A list of revisions to export as revision numbers.
1440 revs: A list of revisions to export as revision numbers.
1441 fntemplate: An optional string to use for generating patch file names.
1441 fntemplate: An optional string to use for generating patch file names.
1442 fp: An optional file-like object to which patches should be written.
1442 fp: An optional file-like object to which patches should be written.
1443 switch_parent: If True, show diffs against second parent when not nullid.
1443 switch_parent: If True, show diffs against second parent when not nullid.
1444 Default is false, which always shows diff against p1.
1444 Default is false, which always shows diff against p1.
1445 opts: diff options to use for generating the patch.
1445 opts: diff options to use for generating the patch.
1446 match: If specified, only export changes to files matching this matcher.
1446 match: If specified, only export changes to files matching this matcher.
1447
1447
1448 Returns:
1448 Returns:
1449 Nothing.
1449 Nothing.
1450
1450
1451 Side Effect:
1451 Side Effect:
1452 "HG Changeset Patch" data is emitted to one of the following
1452 "HG Changeset Patch" data is emitted to one of the following
1453 destinations:
1453 destinations:
1454 fp is specified: All revs are written to the specified
1454 fp is specified: All revs are written to the specified
1455 file-like object.
1455 file-like object.
1456 fntemplate specified: Each rev is written to a unique file named using
1456 fntemplate specified: Each rev is written to a unique file named using
1457 the given template.
1457 the given template.
1458 Neither fp nor template specified: All revs written to repo.ui.write()
1458 Neither fp nor template specified: All revs written to repo.ui.write()
1459 '''
1459 '''
1460
1460
1461 total = len(revs)
1461 total = len(revs)
1462 revwidth = max(len(str(rev)) for rev in revs)
1462 revwidth = max(len(str(rev)) for rev in revs)
1463 filemode = {}
1463 filemode = {}
1464
1464
1465 write = None
1465 write = None
1466 dest = '<unnamed>'
1466 dest = '<unnamed>'
1467 if fp:
1467 if fp:
1468 dest = getattr(fp, 'name', dest)
1468 dest = getattr(fp, 'name', dest)
1469 def write(s, **kw):
1469 def write(s, **kw):
1470 fp.write(s)
1470 fp.write(s)
1471 elif not fntemplate:
1471 elif not fntemplate:
1472 write = repo.ui.write
1472 write = repo.ui.write
1473
1473
1474 for seqno, rev in enumerate(revs, 1):
1474 for seqno, rev in enumerate(revs, 1):
1475 ctx = repo[rev]
1475 ctx = repo[rev]
1476 fo = None
1476 fo = None
1477 if not fp and fntemplate:
1477 if not fp and fntemplate:
1478 desc_lines = ctx.description().rstrip().split('\n')
1478 desc_lines = ctx.description().rstrip().split('\n')
1479 desc = desc_lines[0] #Commit always has a first line.
1479 desc = desc_lines[0] #Commit always has a first line.
1480 fo = makefileobj(repo, fntemplate, ctx.node(), desc=desc,
1480 fo = makefileobj(repo, fntemplate, ctx.node(), desc=desc,
1481 total=total, seqno=seqno, revwidth=revwidth,
1481 total=total, seqno=seqno, revwidth=revwidth,
1482 mode='wb', modemap=filemode)
1482 mode='wb', modemap=filemode)
1483 dest = fo.name
1483 dest = fo.name
1484 def write(s, **kw):
1484 def write(s, **kw):
1485 fo.write(s)
1485 fo.write(s)
1486 if not dest.startswith('<'):
1486 if not dest.startswith('<'):
1487 repo.ui.note("%s\n" % dest)
1487 repo.ui.note("%s\n" % dest)
1488 _exportsingle(
1488 _exportsingle(
1489 repo, ctx, match, switch_parent, rev, seqno, write, opts)
1489 repo, ctx, match, switch_parent, rev, seqno, write, opts)
1490 if fo is not None:
1490 if fo is not None:
1491 fo.close()
1491 fo.close()
1492
1492
1493 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1493 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1494 changes=None, stat=False, fp=None, prefix='',
1494 changes=None, stat=False, fp=None, prefix='',
1495 root='', listsubrepos=False, hunksfilterfn=None):
1495 root='', listsubrepos=False, hunksfilterfn=None):
1496 '''show diff or diffstat.'''
1496 '''show diff or diffstat.'''
1497 if fp is None:
1497 if fp is None:
1498 write = ui.write
1498 write = ui.write
1499 else:
1499 else:
1500 def write(s, **kw):
1500 def write(s, **kw):
1501 fp.write(s)
1501 fp.write(s)
1502
1502
1503 if root:
1503 if root:
1504 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1504 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1505 else:
1505 else:
1506 relroot = ''
1506 relroot = ''
1507 if relroot != '':
1507 if relroot != '':
1508 # XXX relative roots currently don't work if the root is within a
1508 # XXX relative roots currently don't work if the root is within a
1509 # subrepo
1509 # subrepo
1510 uirelroot = match.uipath(relroot)
1510 uirelroot = match.uipath(relroot)
1511 relroot += '/'
1511 relroot += '/'
1512 for matchroot in match.files():
1512 for matchroot in match.files():
1513 if not matchroot.startswith(relroot):
1513 if not matchroot.startswith(relroot):
1514 ui.warn(_('warning: %s not inside relative root %s\n') % (
1514 ui.warn(_('warning: %s not inside relative root %s\n') % (
1515 match.uipath(matchroot), uirelroot))
1515 match.uipath(matchroot), uirelroot))
1516
1516
1517 if stat:
1517 if stat:
1518 diffopts = diffopts.copy(context=0, noprefix=False)
1518 diffopts = diffopts.copy(context=0, noprefix=False)
1519 width = 80
1519 width = 80
1520 if not ui.plain():
1520 if not ui.plain():
1521 width = ui.termwidth()
1521 width = ui.termwidth()
1522 chunks = patch.diff(repo, node1, node2, match, changes, opts=diffopts,
1522 chunks = patch.diff(repo, node1, node2, match, changes, opts=diffopts,
1523 prefix=prefix, relroot=relroot,
1523 prefix=prefix, relroot=relroot,
1524 hunksfilterfn=hunksfilterfn)
1524 hunksfilterfn=hunksfilterfn)
1525 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1525 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1526 width=width):
1526 width=width):
1527 write(chunk, label=label)
1527 write(chunk, label=label)
1528 else:
1528 else:
1529 for chunk, label in patch.diffui(repo, node1, node2, match,
1529 for chunk, label in patch.diffui(repo, node1, node2, match,
1530 changes, opts=diffopts, prefix=prefix,
1530 changes, opts=diffopts, prefix=prefix,
1531 relroot=relroot,
1531 relroot=relroot,
1532 hunksfilterfn=hunksfilterfn):
1532 hunksfilterfn=hunksfilterfn):
1533 write(chunk, label=label)
1533 write(chunk, label=label)
1534
1534
1535 if listsubrepos:
1535 if listsubrepos:
1536 ctx1 = repo[node1]
1536 ctx1 = repo[node1]
1537 ctx2 = repo[node2]
1537 ctx2 = repo[node2]
1538 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1538 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1539 tempnode2 = node2
1539 tempnode2 = node2
1540 try:
1540 try:
1541 if node2 is not None:
1541 if node2 is not None:
1542 tempnode2 = ctx2.substate[subpath][1]
1542 tempnode2 = ctx2.substate[subpath][1]
1543 except KeyError:
1543 except KeyError:
1544 # A subrepo that existed in node1 was deleted between node1 and
1544 # A subrepo that existed in node1 was deleted between node1 and
1545 # node2 (inclusive). Thus, ctx2's substate won't contain that
1545 # node2 (inclusive). Thus, ctx2's substate won't contain that
1546 # subpath. The best we can do is to ignore it.
1546 # subpath. The best we can do is to ignore it.
1547 tempnode2 = None
1547 tempnode2 = None
1548 submatch = matchmod.subdirmatcher(subpath, match)
1548 submatch = matchmod.subdirmatcher(subpath, match)
1549 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1549 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1550 stat=stat, fp=fp, prefix=prefix)
1550 stat=stat, fp=fp, prefix=prefix)
1551
1551
1552 def _changesetlabels(ctx):
1552 def _changesetlabels(ctx):
1553 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1553 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1554 if ctx.obsolete():
1554 if ctx.obsolete():
1555 labels.append('changeset.obsolete')
1555 labels.append('changeset.obsolete')
1556 if ctx.isunstable():
1556 if ctx.isunstable():
1557 labels.append('changeset.unstable')
1557 labels.append('changeset.unstable')
1558 for instability in ctx.instabilities():
1558 for instability in ctx.instabilities():
1559 labels.append('instability.%s' % instability)
1559 labels.append('instability.%s' % instability)
1560 return ' '.join(labels)
1560 return ' '.join(labels)
1561
1561
1562 class changeset_printer(object):
1562 class changeset_printer(object):
1563 '''show changeset information when templating not requested.'''
1563 '''show changeset information when templating not requested.'''
1564
1564
1565 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1565 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1566 self.ui = ui
1566 self.ui = ui
1567 self.repo = repo
1567 self.repo = repo
1568 self.buffered = buffered
1568 self.buffered = buffered
1569 self.matchfn = matchfn
1569 self.matchfn = matchfn
1570 self.diffopts = diffopts
1570 self.diffopts = diffopts
1571 self.header = {}
1571 self.header = {}
1572 self.hunk = {}
1572 self.hunk = {}
1573 self.lastheader = None
1573 self.lastheader = None
1574 self.footer = None
1574 self.footer = None
1575 self._columns = templatekw.getlogcolumns()
1575 self._columns = templatekw.getlogcolumns()
1576
1576
1577 def flush(self, ctx):
1577 def flush(self, ctx):
1578 rev = ctx.rev()
1578 rev = ctx.rev()
1579 if rev in self.header:
1579 if rev in self.header:
1580 h = self.header[rev]
1580 h = self.header[rev]
1581 if h != self.lastheader:
1581 if h != self.lastheader:
1582 self.lastheader = h
1582 self.lastheader = h
1583 self.ui.write(h)
1583 self.ui.write(h)
1584 del self.header[rev]
1584 del self.header[rev]
1585 if rev in self.hunk:
1585 if rev in self.hunk:
1586 self.ui.write(self.hunk[rev])
1586 self.ui.write(self.hunk[rev])
1587 del self.hunk[rev]
1587 del self.hunk[rev]
1588 return 1
1589 return 0
1590
1588
1591 def close(self):
1589 def close(self):
1592 if self.footer:
1590 if self.footer:
1593 self.ui.write(self.footer)
1591 self.ui.write(self.footer)
1594
1592
1595 def show(self, ctx, copies=None, matchfn=None, hunksfilterfn=None,
1593 def show(self, ctx, copies=None, matchfn=None, hunksfilterfn=None,
1596 **props):
1594 **props):
1597 props = pycompat.byteskwargs(props)
1595 props = pycompat.byteskwargs(props)
1598 if self.buffered:
1596 if self.buffered:
1599 self.ui.pushbuffer(labeled=True)
1597 self.ui.pushbuffer(labeled=True)
1600 self._show(ctx, copies, matchfn, hunksfilterfn, props)
1598 self._show(ctx, copies, matchfn, hunksfilterfn, props)
1601 self.hunk[ctx.rev()] = self.ui.popbuffer()
1599 self.hunk[ctx.rev()] = self.ui.popbuffer()
1602 else:
1600 else:
1603 self._show(ctx, copies, matchfn, hunksfilterfn, props)
1601 self._show(ctx, copies, matchfn, hunksfilterfn, props)
1604
1602
1605 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
1603 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
1606 '''show a single changeset or file revision'''
1604 '''show a single changeset or file revision'''
1607 changenode = ctx.node()
1605 changenode = ctx.node()
1608 rev = ctx.rev()
1606 rev = ctx.rev()
1609
1607
1610 if self.ui.quiet:
1608 if self.ui.quiet:
1611 self.ui.write("%s\n" % scmutil.formatchangeid(ctx),
1609 self.ui.write("%s\n" % scmutil.formatchangeid(ctx),
1612 label='log.node')
1610 label='log.node')
1613 return
1611 return
1614
1612
1615 columns = self._columns
1613 columns = self._columns
1616 self.ui.write(columns['changeset'] % scmutil.formatchangeid(ctx),
1614 self.ui.write(columns['changeset'] % scmutil.formatchangeid(ctx),
1617 label=_changesetlabels(ctx))
1615 label=_changesetlabels(ctx))
1618
1616
1619 # branches are shown first before any other names due to backwards
1617 # branches are shown first before any other names due to backwards
1620 # compatibility
1618 # compatibility
1621 branch = ctx.branch()
1619 branch = ctx.branch()
1622 # don't show the default branch name
1620 # don't show the default branch name
1623 if branch != 'default':
1621 if branch != 'default':
1624 self.ui.write(columns['branch'] % branch, label='log.branch')
1622 self.ui.write(columns['branch'] % branch, label='log.branch')
1625
1623
1626 for nsname, ns in self.repo.names.iteritems():
1624 for nsname, ns in self.repo.names.iteritems():
1627 # branches has special logic already handled above, so here we just
1625 # branches has special logic already handled above, so here we just
1628 # skip it
1626 # skip it
1629 if nsname == 'branches':
1627 if nsname == 'branches':
1630 continue
1628 continue
1631 # we will use the templatename as the color name since those two
1629 # we will use the templatename as the color name since those two
1632 # should be the same
1630 # should be the same
1633 for name in ns.names(self.repo, changenode):
1631 for name in ns.names(self.repo, changenode):
1634 self.ui.write(ns.logfmt % name,
1632 self.ui.write(ns.logfmt % name,
1635 label='log.%s' % ns.colorname)
1633 label='log.%s' % ns.colorname)
1636 if self.ui.debugflag:
1634 if self.ui.debugflag:
1637 self.ui.write(columns['phase'] % ctx.phasestr(), label='log.phase')
1635 self.ui.write(columns['phase'] % ctx.phasestr(), label='log.phase')
1638 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1636 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1639 label = 'log.parent changeset.%s' % pctx.phasestr()
1637 label = 'log.parent changeset.%s' % pctx.phasestr()
1640 self.ui.write(columns['parent'] % scmutil.formatchangeid(pctx),
1638 self.ui.write(columns['parent'] % scmutil.formatchangeid(pctx),
1641 label=label)
1639 label=label)
1642
1640
1643 if self.ui.debugflag and rev is not None:
1641 if self.ui.debugflag and rev is not None:
1644 mnode = ctx.manifestnode()
1642 mnode = ctx.manifestnode()
1645 mrev = self.repo.manifestlog._revlog.rev(mnode)
1643 mrev = self.repo.manifestlog._revlog.rev(mnode)
1646 self.ui.write(columns['manifest']
1644 self.ui.write(columns['manifest']
1647 % scmutil.formatrevnode(self.ui, mrev, mnode),
1645 % scmutil.formatrevnode(self.ui, mrev, mnode),
1648 label='ui.debug log.manifest')
1646 label='ui.debug log.manifest')
1649 self.ui.write(columns['user'] % ctx.user(), label='log.user')
1647 self.ui.write(columns['user'] % ctx.user(), label='log.user')
1650 self.ui.write(columns['date'] % util.datestr(ctx.date()),
1648 self.ui.write(columns['date'] % util.datestr(ctx.date()),
1651 label='log.date')
1649 label='log.date')
1652
1650
1653 if ctx.isunstable():
1651 if ctx.isunstable():
1654 instabilities = ctx.instabilities()
1652 instabilities = ctx.instabilities()
1655 self.ui.write(columns['instability'] % ', '.join(instabilities),
1653 self.ui.write(columns['instability'] % ', '.join(instabilities),
1656 label='log.instability')
1654 label='log.instability')
1657
1655
1658 elif ctx.obsolete():
1656 elif ctx.obsolete():
1659 self._showobsfate(ctx)
1657 self._showobsfate(ctx)
1660
1658
1661 self._exthook(ctx)
1659 self._exthook(ctx)
1662
1660
1663 if self.ui.debugflag:
1661 if self.ui.debugflag:
1664 files = ctx.p1().status(ctx)[:3]
1662 files = ctx.p1().status(ctx)[:3]
1665 for key, value in zip(['files', 'files+', 'files-'], files):
1663 for key, value in zip(['files', 'files+', 'files-'], files):
1666 if value:
1664 if value:
1667 self.ui.write(columns[key] % " ".join(value),
1665 self.ui.write(columns[key] % " ".join(value),
1668 label='ui.debug log.files')
1666 label='ui.debug log.files')
1669 elif ctx.files() and self.ui.verbose:
1667 elif ctx.files() and self.ui.verbose:
1670 self.ui.write(columns['files'] % " ".join(ctx.files()),
1668 self.ui.write(columns['files'] % " ".join(ctx.files()),
1671 label='ui.note log.files')
1669 label='ui.note log.files')
1672 if copies and self.ui.verbose:
1670 if copies and self.ui.verbose:
1673 copies = ['%s (%s)' % c for c in copies]
1671 copies = ['%s (%s)' % c for c in copies]
1674 self.ui.write(columns['copies'] % ' '.join(copies),
1672 self.ui.write(columns['copies'] % ' '.join(copies),
1675 label='ui.note log.copies')
1673 label='ui.note log.copies')
1676
1674
1677 extra = ctx.extra()
1675 extra = ctx.extra()
1678 if extra and self.ui.debugflag:
1676 if extra and self.ui.debugflag:
1679 for key, value in sorted(extra.items()):
1677 for key, value in sorted(extra.items()):
1680 self.ui.write(columns['extra'] % (key, util.escapestr(value)),
1678 self.ui.write(columns['extra'] % (key, util.escapestr(value)),
1681 label='ui.debug log.extra')
1679 label='ui.debug log.extra')
1682
1680
1683 description = ctx.description().strip()
1681 description = ctx.description().strip()
1684 if description:
1682 if description:
1685 if self.ui.verbose:
1683 if self.ui.verbose:
1686 self.ui.write(_("description:\n"),
1684 self.ui.write(_("description:\n"),
1687 label='ui.note log.description')
1685 label='ui.note log.description')
1688 self.ui.write(description,
1686 self.ui.write(description,
1689 label='ui.note log.description')
1687 label='ui.note log.description')
1690 self.ui.write("\n\n")
1688 self.ui.write("\n\n")
1691 else:
1689 else:
1692 self.ui.write(columns['summary'] % description.splitlines()[0],
1690 self.ui.write(columns['summary'] % description.splitlines()[0],
1693 label='log.summary')
1691 label='log.summary')
1694 self.ui.write("\n")
1692 self.ui.write("\n")
1695
1693
1696 self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
1694 self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
1697
1695
1698 def _showobsfate(self, ctx):
1696 def _showobsfate(self, ctx):
1699 obsfate = templatekw.showobsfate(repo=self.repo, ctx=ctx, ui=self.ui)
1697 obsfate = templatekw.showobsfate(repo=self.repo, ctx=ctx, ui=self.ui)
1700
1698
1701 if obsfate:
1699 if obsfate:
1702 for obsfateline in obsfate:
1700 for obsfateline in obsfate:
1703 self.ui.write(self._columns['obsolete'] % obsfateline,
1701 self.ui.write(self._columns['obsolete'] % obsfateline,
1704 label='log.obsfate')
1702 label='log.obsfate')
1705
1703
1706 def _exthook(self, ctx):
1704 def _exthook(self, ctx):
1707 '''empty method used by extension as a hook point
1705 '''empty method used by extension as a hook point
1708 '''
1706 '''
1709
1707
1710 def showpatch(self, ctx, matchfn, hunksfilterfn=None):
1708 def showpatch(self, ctx, matchfn, hunksfilterfn=None):
1711 if not matchfn:
1709 if not matchfn:
1712 matchfn = self.matchfn
1710 matchfn = self.matchfn
1713 if matchfn:
1711 if matchfn:
1714 stat = self.diffopts.get('stat')
1712 stat = self.diffopts.get('stat')
1715 diff = self.diffopts.get('patch')
1713 diff = self.diffopts.get('patch')
1716 diffopts = patch.diffallopts(self.ui, self.diffopts)
1714 diffopts = patch.diffallopts(self.ui, self.diffopts)
1717 node = ctx.node()
1715 node = ctx.node()
1718 prev = ctx.p1().node()
1716 prev = ctx.p1().node()
1719 if stat:
1717 if stat:
1720 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1718 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1721 match=matchfn, stat=True,
1719 match=matchfn, stat=True,
1722 hunksfilterfn=hunksfilterfn)
1720 hunksfilterfn=hunksfilterfn)
1723 if diff:
1721 if diff:
1724 if stat:
1722 if stat:
1725 self.ui.write("\n")
1723 self.ui.write("\n")
1726 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1724 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1727 match=matchfn, stat=False,
1725 match=matchfn, stat=False,
1728 hunksfilterfn=hunksfilterfn)
1726 hunksfilterfn=hunksfilterfn)
1729 self.ui.write("\n")
1727 self.ui.write("\n")
1730
1728
1731 class jsonchangeset(changeset_printer):
1729 class jsonchangeset(changeset_printer):
1732 '''format changeset information.'''
1730 '''format changeset information.'''
1733
1731
1734 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1732 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1735 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1733 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1736 self.cache = {}
1734 self.cache = {}
1737 self._first = True
1735 self._first = True
1738
1736
1739 def close(self):
1737 def close(self):
1740 if not self._first:
1738 if not self._first:
1741 self.ui.write("\n]\n")
1739 self.ui.write("\n]\n")
1742 else:
1740 else:
1743 self.ui.write("[]\n")
1741 self.ui.write("[]\n")
1744
1742
1745 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
1743 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
1746 '''show a single changeset or file revision'''
1744 '''show a single changeset or file revision'''
1747 rev = ctx.rev()
1745 rev = ctx.rev()
1748 if rev is None:
1746 if rev is None:
1749 jrev = jnode = 'null'
1747 jrev = jnode = 'null'
1750 else:
1748 else:
1751 jrev = '%d' % rev
1749 jrev = '%d' % rev
1752 jnode = '"%s"' % hex(ctx.node())
1750 jnode = '"%s"' % hex(ctx.node())
1753 j = encoding.jsonescape
1751 j = encoding.jsonescape
1754
1752
1755 if self._first:
1753 if self._first:
1756 self.ui.write("[\n {")
1754 self.ui.write("[\n {")
1757 self._first = False
1755 self._first = False
1758 else:
1756 else:
1759 self.ui.write(",\n {")
1757 self.ui.write(",\n {")
1760
1758
1761 if self.ui.quiet:
1759 if self.ui.quiet:
1762 self.ui.write(('\n "rev": %s') % jrev)
1760 self.ui.write(('\n "rev": %s') % jrev)
1763 self.ui.write((',\n "node": %s') % jnode)
1761 self.ui.write((',\n "node": %s') % jnode)
1764 self.ui.write('\n }')
1762 self.ui.write('\n }')
1765 return
1763 return
1766
1764
1767 self.ui.write(('\n "rev": %s') % jrev)
1765 self.ui.write(('\n "rev": %s') % jrev)
1768 self.ui.write((',\n "node": %s') % jnode)
1766 self.ui.write((',\n "node": %s') % jnode)
1769 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1767 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1770 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1768 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1771 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1769 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1772 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1770 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1773 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1771 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1774
1772
1775 self.ui.write((',\n "bookmarks": [%s]') %
1773 self.ui.write((',\n "bookmarks": [%s]') %
1776 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1774 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1777 self.ui.write((',\n "tags": [%s]') %
1775 self.ui.write((',\n "tags": [%s]') %
1778 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1776 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1779 self.ui.write((',\n "parents": [%s]') %
1777 self.ui.write((',\n "parents": [%s]') %
1780 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1778 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1781
1779
1782 if self.ui.debugflag:
1780 if self.ui.debugflag:
1783 if rev is None:
1781 if rev is None:
1784 jmanifestnode = 'null'
1782 jmanifestnode = 'null'
1785 else:
1783 else:
1786 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1784 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1787 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1785 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1788
1786
1789 self.ui.write((',\n "extra": {%s}') %
1787 self.ui.write((',\n "extra": {%s}') %
1790 ", ".join('"%s": "%s"' % (j(k), j(v))
1788 ", ".join('"%s": "%s"' % (j(k), j(v))
1791 for k, v in ctx.extra().items()))
1789 for k, v in ctx.extra().items()))
1792
1790
1793 files = ctx.p1().status(ctx)
1791 files = ctx.p1().status(ctx)
1794 self.ui.write((',\n "modified": [%s]') %
1792 self.ui.write((',\n "modified": [%s]') %
1795 ", ".join('"%s"' % j(f) for f in files[0]))
1793 ", ".join('"%s"' % j(f) for f in files[0]))
1796 self.ui.write((',\n "added": [%s]') %
1794 self.ui.write((',\n "added": [%s]') %
1797 ", ".join('"%s"' % j(f) for f in files[1]))
1795 ", ".join('"%s"' % j(f) for f in files[1]))
1798 self.ui.write((',\n "removed": [%s]') %
1796 self.ui.write((',\n "removed": [%s]') %
1799 ", ".join('"%s"' % j(f) for f in files[2]))
1797 ", ".join('"%s"' % j(f) for f in files[2]))
1800
1798
1801 elif self.ui.verbose:
1799 elif self.ui.verbose:
1802 self.ui.write((',\n "files": [%s]') %
1800 self.ui.write((',\n "files": [%s]') %
1803 ", ".join('"%s"' % j(f) for f in ctx.files()))
1801 ", ".join('"%s"' % j(f) for f in ctx.files()))
1804
1802
1805 if copies:
1803 if copies:
1806 self.ui.write((',\n "copies": {%s}') %
1804 self.ui.write((',\n "copies": {%s}') %
1807 ", ".join('"%s": "%s"' % (j(k), j(v))
1805 ", ".join('"%s": "%s"' % (j(k), j(v))
1808 for k, v in copies))
1806 for k, v in copies))
1809
1807
1810 matchfn = self.matchfn
1808 matchfn = self.matchfn
1811 if matchfn:
1809 if matchfn:
1812 stat = self.diffopts.get('stat')
1810 stat = self.diffopts.get('stat')
1813 diff = self.diffopts.get('patch')
1811 diff = self.diffopts.get('patch')
1814 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1812 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1815 node, prev = ctx.node(), ctx.p1().node()
1813 node, prev = ctx.node(), ctx.p1().node()
1816 if stat:
1814 if stat:
1817 self.ui.pushbuffer()
1815 self.ui.pushbuffer()
1818 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1816 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1819 match=matchfn, stat=True)
1817 match=matchfn, stat=True)
1820 self.ui.write((',\n "diffstat": "%s"')
1818 self.ui.write((',\n "diffstat": "%s"')
1821 % j(self.ui.popbuffer()))
1819 % j(self.ui.popbuffer()))
1822 if diff:
1820 if diff:
1823 self.ui.pushbuffer()
1821 self.ui.pushbuffer()
1824 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1822 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1825 match=matchfn, stat=False)
1823 match=matchfn, stat=False)
1826 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1824 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1827
1825
1828 self.ui.write("\n }")
1826 self.ui.write("\n }")
1829
1827
1830 class changeset_templater(changeset_printer):
1828 class changeset_templater(changeset_printer):
1831 '''format changeset information.
1829 '''format changeset information.
1832
1830
1833 Note: there are a variety of convenience functions to build a
1831 Note: there are a variety of convenience functions to build a
1834 changeset_templater for common cases. See functions such as:
1832 changeset_templater for common cases. See functions such as:
1835 makelogtemplater, show_changeset, buildcommittemplate, or other
1833 makelogtemplater, show_changeset, buildcommittemplate, or other
1836 functions that use changesest_templater.
1834 functions that use changesest_templater.
1837 '''
1835 '''
1838
1836
1839 # Arguments before "buffered" used to be positional. Consider not
1837 # Arguments before "buffered" used to be positional. Consider not
1840 # adding/removing arguments before "buffered" to not break callers.
1838 # adding/removing arguments before "buffered" to not break callers.
1841 def __init__(self, ui, repo, tmplspec, matchfn=None, diffopts=None,
1839 def __init__(self, ui, repo, tmplspec, matchfn=None, diffopts=None,
1842 buffered=False):
1840 buffered=False):
1843 diffopts = diffopts or {}
1841 diffopts = diffopts or {}
1844
1842
1845 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1843 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1846 tres = formatter.templateresources(ui, repo)
1844 tres = formatter.templateresources(ui, repo)
1847 self.t = formatter.loadtemplater(ui, tmplspec,
1845 self.t = formatter.loadtemplater(ui, tmplspec,
1848 defaults=templatekw.keywords,
1846 defaults=templatekw.keywords,
1849 resources=tres,
1847 resources=tres,
1850 cache=templatekw.defaulttempl)
1848 cache=templatekw.defaulttempl)
1851 self._counter = itertools.count()
1849 self._counter = itertools.count()
1852 self.cache = tres['cache'] # shared with _graphnodeformatter()
1850 self.cache = tres['cache'] # shared with _graphnodeformatter()
1853
1851
1854 self._tref = tmplspec.ref
1852 self._tref = tmplspec.ref
1855 self._parts = {'header': '', 'footer': '',
1853 self._parts = {'header': '', 'footer': '',
1856 tmplspec.ref: tmplspec.ref,
1854 tmplspec.ref: tmplspec.ref,
1857 'docheader': '', 'docfooter': '',
1855 'docheader': '', 'docfooter': '',
1858 'separator': ''}
1856 'separator': ''}
1859 if tmplspec.mapfile:
1857 if tmplspec.mapfile:
1860 # find correct templates for current mode, for backward
1858 # find correct templates for current mode, for backward
1861 # compatibility with 'log -v/-q/--debug' using a mapfile
1859 # compatibility with 'log -v/-q/--debug' using a mapfile
1862 tmplmodes = [
1860 tmplmodes = [
1863 (True, ''),
1861 (True, ''),
1864 (self.ui.verbose, '_verbose'),
1862 (self.ui.verbose, '_verbose'),
1865 (self.ui.quiet, '_quiet'),
1863 (self.ui.quiet, '_quiet'),
1866 (self.ui.debugflag, '_debug'),
1864 (self.ui.debugflag, '_debug'),
1867 ]
1865 ]
1868 for mode, postfix in tmplmodes:
1866 for mode, postfix in tmplmodes:
1869 for t in self._parts:
1867 for t in self._parts:
1870 cur = t + postfix
1868 cur = t + postfix
1871 if mode and cur in self.t:
1869 if mode and cur in self.t:
1872 self._parts[t] = cur
1870 self._parts[t] = cur
1873 else:
1871 else:
1874 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
1872 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
1875 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
1873 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
1876 self._parts.update(m)
1874 self._parts.update(m)
1877
1875
1878 if self._parts['docheader']:
1876 if self._parts['docheader']:
1879 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1877 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1880
1878
1881 def close(self):
1879 def close(self):
1882 if self._parts['docfooter']:
1880 if self._parts['docfooter']:
1883 if not self.footer:
1881 if not self.footer:
1884 self.footer = ""
1882 self.footer = ""
1885 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1883 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1886 return super(changeset_templater, self).close()
1884 return super(changeset_templater, self).close()
1887
1885
1888 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
1886 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
1889 '''show a single changeset or file revision'''
1887 '''show a single changeset or file revision'''
1890 props = props.copy()
1888 props = props.copy()
1891 props['ctx'] = ctx
1889 props['ctx'] = ctx
1892 props['index'] = index = next(self._counter)
1890 props['index'] = index = next(self._counter)
1893 props['revcache'] = {'copies': copies}
1891 props['revcache'] = {'copies': copies}
1894 props = pycompat.strkwargs(props)
1892 props = pycompat.strkwargs(props)
1895
1893
1896 # write separator, which wouldn't work well with the header part below
1894 # write separator, which wouldn't work well with the header part below
1897 # since there's inherently a conflict between header (across items) and
1895 # since there's inherently a conflict between header (across items) and
1898 # separator (per item)
1896 # separator (per item)
1899 if self._parts['separator'] and index > 0:
1897 if self._parts['separator'] and index > 0:
1900 self.ui.write(templater.stringify(self.t(self._parts['separator'])))
1898 self.ui.write(templater.stringify(self.t(self._parts['separator'])))
1901
1899
1902 # write header
1900 # write header
1903 if self._parts['header']:
1901 if self._parts['header']:
1904 h = templater.stringify(self.t(self._parts['header'], **props))
1902 h = templater.stringify(self.t(self._parts['header'], **props))
1905 if self.buffered:
1903 if self.buffered:
1906 self.header[ctx.rev()] = h
1904 self.header[ctx.rev()] = h
1907 else:
1905 else:
1908 if self.lastheader != h:
1906 if self.lastheader != h:
1909 self.lastheader = h
1907 self.lastheader = h
1910 self.ui.write(h)
1908 self.ui.write(h)
1911
1909
1912 # write changeset metadata, then patch if requested
1910 # write changeset metadata, then patch if requested
1913 key = self._parts[self._tref]
1911 key = self._parts[self._tref]
1914 self.ui.write(templater.stringify(self.t(key, **props)))
1912 self.ui.write(templater.stringify(self.t(key, **props)))
1915 self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
1913 self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
1916
1914
1917 if self._parts['footer']:
1915 if self._parts['footer']:
1918 if not self.footer:
1916 if not self.footer:
1919 self.footer = templater.stringify(
1917 self.footer = templater.stringify(
1920 self.t(self._parts['footer'], **props))
1918 self.t(self._parts['footer'], **props))
1921
1919
1922 def logtemplatespec(tmpl, mapfile):
1920 def logtemplatespec(tmpl, mapfile):
1923 if mapfile:
1921 if mapfile:
1924 return formatter.templatespec('changeset', tmpl, mapfile)
1922 return formatter.templatespec('changeset', tmpl, mapfile)
1925 else:
1923 else:
1926 return formatter.templatespec('', tmpl, None)
1924 return formatter.templatespec('', tmpl, None)
1927
1925
1928 def _lookuplogtemplate(ui, tmpl, style):
1926 def _lookuplogtemplate(ui, tmpl, style):
1929 """Find the template matching the given template spec or style
1927 """Find the template matching the given template spec or style
1930
1928
1931 See formatter.lookuptemplate() for details.
1929 See formatter.lookuptemplate() for details.
1932 """
1930 """
1933
1931
1934 # ui settings
1932 # ui settings
1935 if not tmpl and not style: # template are stronger than style
1933 if not tmpl and not style: # template are stronger than style
1936 tmpl = ui.config('ui', 'logtemplate')
1934 tmpl = ui.config('ui', 'logtemplate')
1937 if tmpl:
1935 if tmpl:
1938 return logtemplatespec(templater.unquotestring(tmpl), None)
1936 return logtemplatespec(templater.unquotestring(tmpl), None)
1939 else:
1937 else:
1940 style = util.expandpath(ui.config('ui', 'style'))
1938 style = util.expandpath(ui.config('ui', 'style'))
1941
1939
1942 if not tmpl and style:
1940 if not tmpl and style:
1943 mapfile = style
1941 mapfile = style
1944 if not os.path.split(mapfile)[0]:
1942 if not os.path.split(mapfile)[0]:
1945 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1943 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1946 or templater.templatepath(mapfile))
1944 or templater.templatepath(mapfile))
1947 if mapname:
1945 if mapname:
1948 mapfile = mapname
1946 mapfile = mapname
1949 return logtemplatespec(None, mapfile)
1947 return logtemplatespec(None, mapfile)
1950
1948
1951 if not tmpl:
1949 if not tmpl:
1952 return logtemplatespec(None, None)
1950 return logtemplatespec(None, None)
1953
1951
1954 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1952 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1955
1953
1956 def makelogtemplater(ui, repo, tmpl, buffered=False):
1954 def makelogtemplater(ui, repo, tmpl, buffered=False):
1957 """Create a changeset_templater from a literal template 'tmpl'
1955 """Create a changeset_templater from a literal template 'tmpl'
1958 byte-string."""
1956 byte-string."""
1959 spec = logtemplatespec(tmpl, None)
1957 spec = logtemplatespec(tmpl, None)
1960 return changeset_templater(ui, repo, spec, buffered=buffered)
1958 return changeset_templater(ui, repo, spec, buffered=buffered)
1961
1959
1962 def show_changeset(ui, repo, opts, buffered=False):
1960 def show_changeset(ui, repo, opts, buffered=False):
1963 """show one changeset using template or regular display.
1961 """show one changeset using template or regular display.
1964
1962
1965 Display format will be the first non-empty hit of:
1963 Display format will be the first non-empty hit of:
1966 1. option 'template'
1964 1. option 'template'
1967 2. option 'style'
1965 2. option 'style'
1968 3. [ui] setting 'logtemplate'
1966 3. [ui] setting 'logtemplate'
1969 4. [ui] setting 'style'
1967 4. [ui] setting 'style'
1970 If all of these values are either the unset or the empty string,
1968 If all of these values are either the unset or the empty string,
1971 regular display via changeset_printer() is done.
1969 regular display via changeset_printer() is done.
1972 """
1970 """
1973 # options
1971 # options
1974 match = None
1972 match = None
1975 if opts.get('patch') or opts.get('stat'):
1973 if opts.get('patch') or opts.get('stat'):
1976 match = scmutil.matchall(repo)
1974 match = scmutil.matchall(repo)
1977
1975
1978 if opts.get('template') == 'json':
1976 if opts.get('template') == 'json':
1979 return jsonchangeset(ui, repo, match, opts, buffered)
1977 return jsonchangeset(ui, repo, match, opts, buffered)
1980
1978
1981 spec = _lookuplogtemplate(ui, opts.get('template'), opts.get('style'))
1979 spec = _lookuplogtemplate(ui, opts.get('template'), opts.get('style'))
1982
1980
1983 if not spec.ref and not spec.tmpl and not spec.mapfile:
1981 if not spec.ref and not spec.tmpl and not spec.mapfile:
1984 return changeset_printer(ui, repo, match, opts, buffered)
1982 return changeset_printer(ui, repo, match, opts, buffered)
1985
1983
1986 return changeset_templater(ui, repo, spec, match, opts, buffered)
1984 return changeset_templater(ui, repo, spec, match, opts, buffered)
1987
1985
1988 def showmarker(fm, marker, index=None):
1986 def showmarker(fm, marker, index=None):
1989 """utility function to display obsolescence marker in a readable way
1987 """utility function to display obsolescence marker in a readable way
1990
1988
1991 To be used by debug function."""
1989 To be used by debug function."""
1992 if index is not None:
1990 if index is not None:
1993 fm.write('index', '%i ', index)
1991 fm.write('index', '%i ', index)
1994 fm.write('prednode', '%s ', hex(marker.prednode()))
1992 fm.write('prednode', '%s ', hex(marker.prednode()))
1995 succs = marker.succnodes()
1993 succs = marker.succnodes()
1996 fm.condwrite(succs, 'succnodes', '%s ',
1994 fm.condwrite(succs, 'succnodes', '%s ',
1997 fm.formatlist(map(hex, succs), name='node'))
1995 fm.formatlist(map(hex, succs), name='node'))
1998 fm.write('flag', '%X ', marker.flags())
1996 fm.write('flag', '%X ', marker.flags())
1999 parents = marker.parentnodes()
1997 parents = marker.parentnodes()
2000 if parents is not None:
1998 if parents is not None:
2001 fm.write('parentnodes', '{%s} ',
1999 fm.write('parentnodes', '{%s} ',
2002 fm.formatlist(map(hex, parents), name='node', sep=', '))
2000 fm.formatlist(map(hex, parents), name='node', sep=', '))
2003 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
2001 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
2004 meta = marker.metadata().copy()
2002 meta = marker.metadata().copy()
2005 meta.pop('date', None)
2003 meta.pop('date', None)
2006 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
2004 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
2007 fm.plain('\n')
2005 fm.plain('\n')
2008
2006
2009 def finddate(ui, repo, date):
2007 def finddate(ui, repo, date):
2010 """Find the tipmost changeset that matches the given date spec"""
2008 """Find the tipmost changeset that matches the given date spec"""
2011
2009
2012 df = util.matchdate(date)
2010 df = util.matchdate(date)
2013 m = scmutil.matchall(repo)
2011 m = scmutil.matchall(repo)
2014 results = {}
2012 results = {}
2015
2013
2016 def prep(ctx, fns):
2014 def prep(ctx, fns):
2017 d = ctx.date()
2015 d = ctx.date()
2018 if df(d[0]):
2016 if df(d[0]):
2019 results[ctx.rev()] = d
2017 results[ctx.rev()] = d
2020
2018
2021 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
2019 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
2022 rev = ctx.rev()
2020 rev = ctx.rev()
2023 if rev in results:
2021 if rev in results:
2024 ui.status(_("found revision %s from %s\n") %
2022 ui.status(_("found revision %s from %s\n") %
2025 (rev, util.datestr(results[rev])))
2023 (rev, util.datestr(results[rev])))
2026 return '%d' % rev
2024 return '%d' % rev
2027
2025
2028 raise error.Abort(_("revision matching date not found"))
2026 raise error.Abort(_("revision matching date not found"))
2029
2027
2030 def increasingwindows(windowsize=8, sizelimit=512):
2028 def increasingwindows(windowsize=8, sizelimit=512):
2031 while True:
2029 while True:
2032 yield windowsize
2030 yield windowsize
2033 if windowsize < sizelimit:
2031 if windowsize < sizelimit:
2034 windowsize *= 2
2032 windowsize *= 2
2035
2033
2036 class FileWalkError(Exception):
2034 class FileWalkError(Exception):
2037 pass
2035 pass
2038
2036
2039 def walkfilerevs(repo, match, follow, revs, fncache):
2037 def walkfilerevs(repo, match, follow, revs, fncache):
2040 '''Walks the file history for the matched files.
2038 '''Walks the file history for the matched files.
2041
2039
2042 Returns the changeset revs that are involved in the file history.
2040 Returns the changeset revs that are involved in the file history.
2043
2041
2044 Throws FileWalkError if the file history can't be walked using
2042 Throws FileWalkError if the file history can't be walked using
2045 filelogs alone.
2043 filelogs alone.
2046 '''
2044 '''
2047 wanted = set()
2045 wanted = set()
2048 copies = []
2046 copies = []
2049 minrev, maxrev = min(revs), max(revs)
2047 minrev, maxrev = min(revs), max(revs)
2050 def filerevgen(filelog, last):
2048 def filerevgen(filelog, last):
2051 """
2049 """
2052 Only files, no patterns. Check the history of each file.
2050 Only files, no patterns. Check the history of each file.
2053
2051
2054 Examines filelog entries within minrev, maxrev linkrev range
2052 Examines filelog entries within minrev, maxrev linkrev range
2055 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
2053 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
2056 tuples in backwards order
2054 tuples in backwards order
2057 """
2055 """
2058 cl_count = len(repo)
2056 cl_count = len(repo)
2059 revs = []
2057 revs = []
2060 for j in xrange(0, last + 1):
2058 for j in xrange(0, last + 1):
2061 linkrev = filelog.linkrev(j)
2059 linkrev = filelog.linkrev(j)
2062 if linkrev < minrev:
2060 if linkrev < minrev:
2063 continue
2061 continue
2064 # only yield rev for which we have the changelog, it can
2062 # only yield rev for which we have the changelog, it can
2065 # happen while doing "hg log" during a pull or commit
2063 # happen while doing "hg log" during a pull or commit
2066 if linkrev >= cl_count:
2064 if linkrev >= cl_count:
2067 break
2065 break
2068
2066
2069 parentlinkrevs = []
2067 parentlinkrevs = []
2070 for p in filelog.parentrevs(j):
2068 for p in filelog.parentrevs(j):
2071 if p != nullrev:
2069 if p != nullrev:
2072 parentlinkrevs.append(filelog.linkrev(p))
2070 parentlinkrevs.append(filelog.linkrev(p))
2073 n = filelog.node(j)
2071 n = filelog.node(j)
2074 revs.append((linkrev, parentlinkrevs,
2072 revs.append((linkrev, parentlinkrevs,
2075 follow and filelog.renamed(n)))
2073 follow and filelog.renamed(n)))
2076
2074
2077 return reversed(revs)
2075 return reversed(revs)
2078 def iterfiles():
2076 def iterfiles():
2079 pctx = repo['.']
2077 pctx = repo['.']
2080 for filename in match.files():
2078 for filename in match.files():
2081 if follow:
2079 if follow:
2082 if filename not in pctx:
2080 if filename not in pctx:
2083 raise error.Abort(_('cannot follow file not in parent '
2081 raise error.Abort(_('cannot follow file not in parent '
2084 'revision: "%s"') % filename)
2082 'revision: "%s"') % filename)
2085 yield filename, pctx[filename].filenode()
2083 yield filename, pctx[filename].filenode()
2086 else:
2084 else:
2087 yield filename, None
2085 yield filename, None
2088 for filename_node in copies:
2086 for filename_node in copies:
2089 yield filename_node
2087 yield filename_node
2090
2088
2091 for file_, node in iterfiles():
2089 for file_, node in iterfiles():
2092 filelog = repo.file(file_)
2090 filelog = repo.file(file_)
2093 if not len(filelog):
2091 if not len(filelog):
2094 if node is None:
2092 if node is None:
2095 # A zero count may be a directory or deleted file, so
2093 # A zero count may be a directory or deleted file, so
2096 # try to find matching entries on the slow path.
2094 # try to find matching entries on the slow path.
2097 if follow:
2095 if follow:
2098 raise error.Abort(
2096 raise error.Abort(
2099 _('cannot follow nonexistent file: "%s"') % file_)
2097 _('cannot follow nonexistent file: "%s"') % file_)
2100 raise FileWalkError("Cannot walk via filelog")
2098 raise FileWalkError("Cannot walk via filelog")
2101 else:
2099 else:
2102 continue
2100 continue
2103
2101
2104 if node is None:
2102 if node is None:
2105 last = len(filelog) - 1
2103 last = len(filelog) - 1
2106 else:
2104 else:
2107 last = filelog.rev(node)
2105 last = filelog.rev(node)
2108
2106
2109 # keep track of all ancestors of the file
2107 # keep track of all ancestors of the file
2110 ancestors = {filelog.linkrev(last)}
2108 ancestors = {filelog.linkrev(last)}
2111
2109
2112 # iterate from latest to oldest revision
2110 # iterate from latest to oldest revision
2113 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
2111 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
2114 if not follow:
2112 if not follow:
2115 if rev > maxrev:
2113 if rev > maxrev:
2116 continue
2114 continue
2117 else:
2115 else:
2118 # Note that last might not be the first interesting
2116 # Note that last might not be the first interesting
2119 # rev to us:
2117 # rev to us:
2120 # if the file has been changed after maxrev, we'll
2118 # if the file has been changed after maxrev, we'll
2121 # have linkrev(last) > maxrev, and we still need
2119 # have linkrev(last) > maxrev, and we still need
2122 # to explore the file graph
2120 # to explore the file graph
2123 if rev not in ancestors:
2121 if rev not in ancestors:
2124 continue
2122 continue
2125 # XXX insert 1327 fix here
2123 # XXX insert 1327 fix here
2126 if flparentlinkrevs:
2124 if flparentlinkrevs:
2127 ancestors.update(flparentlinkrevs)
2125 ancestors.update(flparentlinkrevs)
2128
2126
2129 fncache.setdefault(rev, []).append(file_)
2127 fncache.setdefault(rev, []).append(file_)
2130 wanted.add(rev)
2128 wanted.add(rev)
2131 if copied:
2129 if copied:
2132 copies.append(copied)
2130 copies.append(copied)
2133
2131
2134 return wanted
2132 return wanted
2135
2133
2136 class _followfilter(object):
2134 class _followfilter(object):
2137 def __init__(self, repo, onlyfirst=False):
2135 def __init__(self, repo, onlyfirst=False):
2138 self.repo = repo
2136 self.repo = repo
2139 self.startrev = nullrev
2137 self.startrev = nullrev
2140 self.roots = set()
2138 self.roots = set()
2141 self.onlyfirst = onlyfirst
2139 self.onlyfirst = onlyfirst
2142
2140
2143 def match(self, rev):
2141 def match(self, rev):
2144 def realparents(rev):
2142 def realparents(rev):
2145 if self.onlyfirst:
2143 if self.onlyfirst:
2146 return self.repo.changelog.parentrevs(rev)[0:1]
2144 return self.repo.changelog.parentrevs(rev)[0:1]
2147 else:
2145 else:
2148 return filter(lambda x: x != nullrev,
2146 return filter(lambda x: x != nullrev,
2149 self.repo.changelog.parentrevs(rev))
2147 self.repo.changelog.parentrevs(rev))
2150
2148
2151 if self.startrev == nullrev:
2149 if self.startrev == nullrev:
2152 self.startrev = rev
2150 self.startrev = rev
2153 return True
2151 return True
2154
2152
2155 if rev > self.startrev:
2153 if rev > self.startrev:
2156 # forward: all descendants
2154 # forward: all descendants
2157 if not self.roots:
2155 if not self.roots:
2158 self.roots.add(self.startrev)
2156 self.roots.add(self.startrev)
2159 for parent in realparents(rev):
2157 for parent in realparents(rev):
2160 if parent in self.roots:
2158 if parent in self.roots:
2161 self.roots.add(rev)
2159 self.roots.add(rev)
2162 return True
2160 return True
2163 else:
2161 else:
2164 # backwards: all parents
2162 # backwards: all parents
2165 if not self.roots:
2163 if not self.roots:
2166 self.roots.update(realparents(self.startrev))
2164 self.roots.update(realparents(self.startrev))
2167 if rev in self.roots:
2165 if rev in self.roots:
2168 self.roots.remove(rev)
2166 self.roots.remove(rev)
2169 self.roots.update(realparents(rev))
2167 self.roots.update(realparents(rev))
2170 return True
2168 return True
2171
2169
2172 return False
2170 return False
2173
2171
2174 def walkchangerevs(repo, match, opts, prepare):
2172 def walkchangerevs(repo, match, opts, prepare):
2175 '''Iterate over files and the revs in which they changed.
2173 '''Iterate over files and the revs in which they changed.
2176
2174
2177 Callers most commonly need to iterate backwards over the history
2175 Callers most commonly need to iterate backwards over the history
2178 in which they are interested. Doing so has awful (quadratic-looking)
2176 in which they are interested. Doing so has awful (quadratic-looking)
2179 performance, so we use iterators in a "windowed" way.
2177 performance, so we use iterators in a "windowed" way.
2180
2178
2181 We walk a window of revisions in the desired order. Within the
2179 We walk a window of revisions in the desired order. Within the
2182 window, we first walk forwards to gather data, then in the desired
2180 window, we first walk forwards to gather data, then in the desired
2183 order (usually backwards) to display it.
2181 order (usually backwards) to display it.
2184
2182
2185 This function returns an iterator yielding contexts. Before
2183 This function returns an iterator yielding contexts. Before
2186 yielding each context, the iterator will first call the prepare
2184 yielding each context, the iterator will first call the prepare
2187 function on each context in the window in forward order.'''
2185 function on each context in the window in forward order.'''
2188
2186
2189 follow = opts.get('follow') or opts.get('follow_first')
2187 follow = opts.get('follow') or opts.get('follow_first')
2190 revs = _logrevs(repo, opts)
2188 revs = _logrevs(repo, opts)
2191 if not revs:
2189 if not revs:
2192 return []
2190 return []
2193 wanted = set()
2191 wanted = set()
2194 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
2192 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
2195 fncache = {}
2193 fncache = {}
2196 change = repo.changectx
2194 change = repo.changectx
2197
2195
2198 # First step is to fill wanted, the set of revisions that we want to yield.
2196 # First step is to fill wanted, the set of revisions that we want to yield.
2199 # When it does not induce extra cost, we also fill fncache for revisions in
2197 # When it does not induce extra cost, we also fill fncache for revisions in
2200 # wanted: a cache of filenames that were changed (ctx.files()) and that
2198 # wanted: a cache of filenames that were changed (ctx.files()) and that
2201 # match the file filtering conditions.
2199 # match the file filtering conditions.
2202
2200
2203 if match.always():
2201 if match.always():
2204 # No files, no patterns. Display all revs.
2202 # No files, no patterns. Display all revs.
2205 wanted = revs
2203 wanted = revs
2206 elif not slowpath:
2204 elif not slowpath:
2207 # We only have to read through the filelog to find wanted revisions
2205 # We only have to read through the filelog to find wanted revisions
2208
2206
2209 try:
2207 try:
2210 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2208 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2211 except FileWalkError:
2209 except FileWalkError:
2212 slowpath = True
2210 slowpath = True
2213
2211
2214 # We decided to fall back to the slowpath because at least one
2212 # We decided to fall back to the slowpath because at least one
2215 # of the paths was not a file. Check to see if at least one of them
2213 # of the paths was not a file. Check to see if at least one of them
2216 # existed in history, otherwise simply return
2214 # existed in history, otherwise simply return
2217 for path in match.files():
2215 for path in match.files():
2218 if path == '.' or path in repo.store:
2216 if path == '.' or path in repo.store:
2219 break
2217 break
2220 else:
2218 else:
2221 return []
2219 return []
2222
2220
2223 if slowpath:
2221 if slowpath:
2224 # We have to read the changelog to match filenames against
2222 # We have to read the changelog to match filenames against
2225 # changed files
2223 # changed files
2226
2224
2227 if follow:
2225 if follow:
2228 raise error.Abort(_('can only follow copies/renames for explicit '
2226 raise error.Abort(_('can only follow copies/renames for explicit '
2229 'filenames'))
2227 'filenames'))
2230
2228
2231 # The slow path checks files modified in every changeset.
2229 # The slow path checks files modified in every changeset.
2232 # This is really slow on large repos, so compute the set lazily.
2230 # This is really slow on large repos, so compute the set lazily.
2233 class lazywantedset(object):
2231 class lazywantedset(object):
2234 def __init__(self):
2232 def __init__(self):
2235 self.set = set()
2233 self.set = set()
2236 self.revs = set(revs)
2234 self.revs = set(revs)
2237
2235
2238 # No need to worry about locality here because it will be accessed
2236 # No need to worry about locality here because it will be accessed
2239 # in the same order as the increasing window below.
2237 # in the same order as the increasing window below.
2240 def __contains__(self, value):
2238 def __contains__(self, value):
2241 if value in self.set:
2239 if value in self.set:
2242 return True
2240 return True
2243 elif not value in self.revs:
2241 elif not value in self.revs:
2244 return False
2242 return False
2245 else:
2243 else:
2246 self.revs.discard(value)
2244 self.revs.discard(value)
2247 ctx = change(value)
2245 ctx = change(value)
2248 matches = filter(match, ctx.files())
2246 matches = filter(match, ctx.files())
2249 if matches:
2247 if matches:
2250 fncache[value] = matches
2248 fncache[value] = matches
2251 self.set.add(value)
2249 self.set.add(value)
2252 return True
2250 return True
2253 return False
2251 return False
2254
2252
2255 def discard(self, value):
2253 def discard(self, value):
2256 self.revs.discard(value)
2254 self.revs.discard(value)
2257 self.set.discard(value)
2255 self.set.discard(value)
2258
2256
2259 wanted = lazywantedset()
2257 wanted = lazywantedset()
2260
2258
2261 # it might be worthwhile to do this in the iterator if the rev range
2259 # it might be worthwhile to do this in the iterator if the rev range
2262 # is descending and the prune args are all within that range
2260 # is descending and the prune args are all within that range
2263 for rev in opts.get('prune', ()):
2261 for rev in opts.get('prune', ()):
2264 rev = repo[rev].rev()
2262 rev = repo[rev].rev()
2265 ff = _followfilter(repo)
2263 ff = _followfilter(repo)
2266 stop = min(revs[0], revs[-1])
2264 stop = min(revs[0], revs[-1])
2267 for x in xrange(rev, stop - 1, -1):
2265 for x in xrange(rev, stop - 1, -1):
2268 if ff.match(x):
2266 if ff.match(x):
2269 wanted = wanted - [x]
2267 wanted = wanted - [x]
2270
2268
2271 # Now that wanted is correctly initialized, we can iterate over the
2269 # Now that wanted is correctly initialized, we can iterate over the
2272 # revision range, yielding only revisions in wanted.
2270 # revision range, yielding only revisions in wanted.
2273 def iterate():
2271 def iterate():
2274 if follow and match.always():
2272 if follow and match.always():
2275 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
2273 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
2276 def want(rev):
2274 def want(rev):
2277 return ff.match(rev) and rev in wanted
2275 return ff.match(rev) and rev in wanted
2278 else:
2276 else:
2279 def want(rev):
2277 def want(rev):
2280 return rev in wanted
2278 return rev in wanted
2281
2279
2282 it = iter(revs)
2280 it = iter(revs)
2283 stopiteration = False
2281 stopiteration = False
2284 for windowsize in increasingwindows():
2282 for windowsize in increasingwindows():
2285 nrevs = []
2283 nrevs = []
2286 for i in xrange(windowsize):
2284 for i in xrange(windowsize):
2287 rev = next(it, None)
2285 rev = next(it, None)
2288 if rev is None:
2286 if rev is None:
2289 stopiteration = True
2287 stopiteration = True
2290 break
2288 break
2291 elif want(rev):
2289 elif want(rev):
2292 nrevs.append(rev)
2290 nrevs.append(rev)
2293 for rev in sorted(nrevs):
2291 for rev in sorted(nrevs):
2294 fns = fncache.get(rev)
2292 fns = fncache.get(rev)
2295 ctx = change(rev)
2293 ctx = change(rev)
2296 if not fns:
2294 if not fns:
2297 def fns_generator():
2295 def fns_generator():
2298 for f in ctx.files():
2296 for f in ctx.files():
2299 if match(f):
2297 if match(f):
2300 yield f
2298 yield f
2301 fns = fns_generator()
2299 fns = fns_generator()
2302 prepare(ctx, fns)
2300 prepare(ctx, fns)
2303 for rev in nrevs:
2301 for rev in nrevs:
2304 yield change(rev)
2302 yield change(rev)
2305
2303
2306 if stopiteration:
2304 if stopiteration:
2307 break
2305 break
2308
2306
2309 return iterate()
2307 return iterate()
2310
2308
2311 def _makefollowlogfilematcher(repo, files, followfirst):
2309 def _makefollowlogfilematcher(repo, files, followfirst):
2312 # When displaying a revision with --patch --follow FILE, we have
2310 # When displaying a revision with --patch --follow FILE, we have
2313 # to know which file of the revision must be diffed. With
2311 # to know which file of the revision must be diffed. With
2314 # --follow, we want the names of the ancestors of FILE in the
2312 # --follow, we want the names of the ancestors of FILE in the
2315 # revision, stored in "fcache". "fcache" is populated by
2313 # revision, stored in "fcache". "fcache" is populated by
2316 # reproducing the graph traversal already done by --follow revset
2314 # reproducing the graph traversal already done by --follow revset
2317 # and relating revs to file names (which is not "correct" but
2315 # and relating revs to file names (which is not "correct" but
2318 # good enough).
2316 # good enough).
2319 fcache = {}
2317 fcache = {}
2320 fcacheready = [False]
2318 fcacheready = [False]
2321 pctx = repo['.']
2319 pctx = repo['.']
2322
2320
2323 def populate():
2321 def populate():
2324 for fn in files:
2322 for fn in files:
2325 fctx = pctx[fn]
2323 fctx = pctx[fn]
2326 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
2324 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
2327 for c in fctx.ancestors(followfirst=followfirst):
2325 for c in fctx.ancestors(followfirst=followfirst):
2328 fcache.setdefault(c.rev(), set()).add(c.path())
2326 fcache.setdefault(c.rev(), set()).add(c.path())
2329
2327
2330 def filematcher(rev):
2328 def filematcher(rev):
2331 if not fcacheready[0]:
2329 if not fcacheready[0]:
2332 # Lazy initialization
2330 # Lazy initialization
2333 fcacheready[0] = True
2331 fcacheready[0] = True
2334 populate()
2332 populate()
2335 return scmutil.matchfiles(repo, fcache.get(rev, []))
2333 return scmutil.matchfiles(repo, fcache.get(rev, []))
2336
2334
2337 return filematcher
2335 return filematcher
2338
2336
2339 def _makenofollowlogfilematcher(repo, pats, opts):
2337 def _makenofollowlogfilematcher(repo, pats, opts):
2340 '''hook for extensions to override the filematcher for non-follow cases'''
2338 '''hook for extensions to override the filematcher for non-follow cases'''
2341 return None
2339 return None
2342
2340
2343 def _makelogrevset(repo, pats, opts, revs):
2341 def _makelogrevset(repo, pats, opts, revs):
2344 """Return (expr, filematcher) where expr is a revset string built
2342 """Return (expr, filematcher) where expr is a revset string built
2345 from log options and file patterns or None. If --stat or --patch
2343 from log options and file patterns or None. If --stat or --patch
2346 are not passed filematcher is None. Otherwise it is a callable
2344 are not passed filematcher is None. Otherwise it is a callable
2347 taking a revision number and returning a match objects filtering
2345 taking a revision number and returning a match objects filtering
2348 the files to be detailed when displaying the revision.
2346 the files to be detailed when displaying the revision.
2349 """
2347 """
2350 opt2revset = {
2348 opt2revset = {
2351 'no_merges': ('not merge()', None),
2349 'no_merges': ('not merge()', None),
2352 'only_merges': ('merge()', None),
2350 'only_merges': ('merge()', None),
2353 '_ancestors': ('ancestors(%(val)s)', None),
2351 '_ancestors': ('ancestors(%(val)s)', None),
2354 '_fancestors': ('_firstancestors(%(val)s)', None),
2352 '_fancestors': ('_firstancestors(%(val)s)', None),
2355 '_descendants': ('descendants(%(val)s)', None),
2353 '_descendants': ('descendants(%(val)s)', None),
2356 '_fdescendants': ('_firstdescendants(%(val)s)', None),
2354 '_fdescendants': ('_firstdescendants(%(val)s)', None),
2357 '_matchfiles': ('_matchfiles(%(val)s)', None),
2355 '_matchfiles': ('_matchfiles(%(val)s)', None),
2358 'date': ('date(%(val)r)', None),
2356 'date': ('date(%(val)r)', None),
2359 'branch': ('branch(%(val)r)', ' or '),
2357 'branch': ('branch(%(val)r)', ' or '),
2360 '_patslog': ('filelog(%(val)r)', ' or '),
2358 '_patslog': ('filelog(%(val)r)', ' or '),
2361 '_patsfollow': ('follow(%(val)r)', ' or '),
2359 '_patsfollow': ('follow(%(val)r)', ' or '),
2362 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
2360 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
2363 'keyword': ('keyword(%(val)r)', ' or '),
2361 'keyword': ('keyword(%(val)r)', ' or '),
2364 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
2362 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
2365 'user': ('user(%(val)r)', ' or '),
2363 'user': ('user(%(val)r)', ' or '),
2366 }
2364 }
2367
2365
2368 opts = dict(opts)
2366 opts = dict(opts)
2369 # follow or not follow?
2367 # follow or not follow?
2370 follow = opts.get('follow') or opts.get('follow_first')
2368 follow = opts.get('follow') or opts.get('follow_first')
2371 if opts.get('follow_first'):
2369 if opts.get('follow_first'):
2372 followfirst = 1
2370 followfirst = 1
2373 else:
2371 else:
2374 followfirst = 0
2372 followfirst = 0
2375 # --follow with FILE behavior depends on revs...
2373 # --follow with FILE behavior depends on revs...
2376 it = iter(revs)
2374 it = iter(revs)
2377 startrev = next(it)
2375 startrev = next(it)
2378 followdescendants = startrev < next(it, startrev)
2376 followdescendants = startrev < next(it, startrev)
2379
2377
2380 # branch and only_branch are really aliases and must be handled at
2378 # branch and only_branch are really aliases and must be handled at
2381 # the same time
2379 # the same time
2382 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
2380 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
2383 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
2381 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
2384 # pats/include/exclude are passed to match.match() directly in
2382 # pats/include/exclude are passed to match.match() directly in
2385 # _matchfiles() revset but walkchangerevs() builds its matcher with
2383 # _matchfiles() revset but walkchangerevs() builds its matcher with
2386 # scmutil.match(). The difference is input pats are globbed on
2384 # scmutil.match(). The difference is input pats are globbed on
2387 # platforms without shell expansion (windows).
2385 # platforms without shell expansion (windows).
2388 wctx = repo[None]
2386 wctx = repo[None]
2389 match, pats = scmutil.matchandpats(wctx, pats, opts)
2387 match, pats = scmutil.matchandpats(wctx, pats, opts)
2390 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
2388 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
2391 if not slowpath:
2389 if not slowpath:
2392 for f in match.files():
2390 for f in match.files():
2393 if follow and f not in wctx:
2391 if follow and f not in wctx:
2394 # If the file exists, it may be a directory, so let it
2392 # If the file exists, it may be a directory, so let it
2395 # take the slow path.
2393 # take the slow path.
2396 if os.path.exists(repo.wjoin(f)):
2394 if os.path.exists(repo.wjoin(f)):
2397 slowpath = True
2395 slowpath = True
2398 continue
2396 continue
2399 else:
2397 else:
2400 raise error.Abort(_('cannot follow file not in parent '
2398 raise error.Abort(_('cannot follow file not in parent '
2401 'revision: "%s"') % f)
2399 'revision: "%s"') % f)
2402 filelog = repo.file(f)
2400 filelog = repo.file(f)
2403 if not filelog:
2401 if not filelog:
2404 # A zero count may be a directory or deleted file, so
2402 # A zero count may be a directory or deleted file, so
2405 # try to find matching entries on the slow path.
2403 # try to find matching entries on the slow path.
2406 if follow:
2404 if follow:
2407 raise error.Abort(
2405 raise error.Abort(
2408 _('cannot follow nonexistent file: "%s"') % f)
2406 _('cannot follow nonexistent file: "%s"') % f)
2409 slowpath = True
2407 slowpath = True
2410
2408
2411 # We decided to fall back to the slowpath because at least one
2409 # We decided to fall back to the slowpath because at least one
2412 # of the paths was not a file. Check to see if at least one of them
2410 # of the paths was not a file. Check to see if at least one of them
2413 # existed in history - in that case, we'll continue down the
2411 # existed in history - in that case, we'll continue down the
2414 # slowpath; otherwise, we can turn off the slowpath
2412 # slowpath; otherwise, we can turn off the slowpath
2415 if slowpath:
2413 if slowpath:
2416 for path in match.files():
2414 for path in match.files():
2417 if path == '.' or path in repo.store:
2415 if path == '.' or path in repo.store:
2418 break
2416 break
2419 else:
2417 else:
2420 slowpath = False
2418 slowpath = False
2421
2419
2422 fpats = ('_patsfollow', '_patsfollowfirst')
2420 fpats = ('_patsfollow', '_patsfollowfirst')
2423 fnopats = (('_ancestors', '_fancestors'),
2421 fnopats = (('_ancestors', '_fancestors'),
2424 ('_descendants', '_fdescendants'))
2422 ('_descendants', '_fdescendants'))
2425 if slowpath:
2423 if slowpath:
2426 # See walkchangerevs() slow path.
2424 # See walkchangerevs() slow path.
2427 #
2425 #
2428 # pats/include/exclude cannot be represented as separate
2426 # pats/include/exclude cannot be represented as separate
2429 # revset expressions as their filtering logic applies at file
2427 # revset expressions as their filtering logic applies at file
2430 # level. For instance "-I a -X a" matches a revision touching
2428 # level. For instance "-I a -X a" matches a revision touching
2431 # "a" and "b" while "file(a) and not file(b)" does
2429 # "a" and "b" while "file(a) and not file(b)" does
2432 # not. Besides, filesets are evaluated against the working
2430 # not. Besides, filesets are evaluated against the working
2433 # directory.
2431 # directory.
2434 matchargs = ['r:', 'd:relpath']
2432 matchargs = ['r:', 'd:relpath']
2435 for p in pats:
2433 for p in pats:
2436 matchargs.append('p:' + p)
2434 matchargs.append('p:' + p)
2437 for p in opts.get('include', []):
2435 for p in opts.get('include', []):
2438 matchargs.append('i:' + p)
2436 matchargs.append('i:' + p)
2439 for p in opts.get('exclude', []):
2437 for p in opts.get('exclude', []):
2440 matchargs.append('x:' + p)
2438 matchargs.append('x:' + p)
2441 matchargs = ','.join(('%r' % p) for p in matchargs)
2439 matchargs = ','.join(('%r' % p) for p in matchargs)
2442 opts['_matchfiles'] = matchargs
2440 opts['_matchfiles'] = matchargs
2443 if follow:
2441 if follow:
2444 opts[fnopats[0][followfirst]] = '.'
2442 opts[fnopats[0][followfirst]] = '.'
2445 else:
2443 else:
2446 if follow:
2444 if follow:
2447 if pats:
2445 if pats:
2448 # follow() revset interprets its file argument as a
2446 # follow() revset interprets its file argument as a
2449 # manifest entry, so use match.files(), not pats.
2447 # manifest entry, so use match.files(), not pats.
2450 opts[fpats[followfirst]] = list(match.files())
2448 opts[fpats[followfirst]] = list(match.files())
2451 else:
2449 else:
2452 op = fnopats[followdescendants][followfirst]
2450 op = fnopats[followdescendants][followfirst]
2453 opts[op] = 'rev(%d)' % startrev
2451 opts[op] = 'rev(%d)' % startrev
2454 else:
2452 else:
2455 opts['_patslog'] = list(pats)
2453 opts['_patslog'] = list(pats)
2456
2454
2457 filematcher = None
2455 filematcher = None
2458 if opts.get('patch') or opts.get('stat'):
2456 if opts.get('patch') or opts.get('stat'):
2459 # When following files, track renames via a special matcher.
2457 # When following files, track renames via a special matcher.
2460 # If we're forced to take the slowpath it means we're following
2458 # If we're forced to take the slowpath it means we're following
2461 # at least one pattern/directory, so don't bother with rename tracking.
2459 # at least one pattern/directory, so don't bother with rename tracking.
2462 if follow and not match.always() and not slowpath:
2460 if follow and not match.always() and not slowpath:
2463 # _makefollowlogfilematcher expects its files argument to be
2461 # _makefollowlogfilematcher expects its files argument to be
2464 # relative to the repo root, so use match.files(), not pats.
2462 # relative to the repo root, so use match.files(), not pats.
2465 filematcher = _makefollowlogfilematcher(repo, match.files(),
2463 filematcher = _makefollowlogfilematcher(repo, match.files(),
2466 followfirst)
2464 followfirst)
2467 else:
2465 else:
2468 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2466 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2469 if filematcher is None:
2467 if filematcher is None:
2470 filematcher = lambda rev: match
2468 filematcher = lambda rev: match
2471
2469
2472 expr = []
2470 expr = []
2473 for op, val in sorted(opts.iteritems()):
2471 for op, val in sorted(opts.iteritems()):
2474 if not val:
2472 if not val:
2475 continue
2473 continue
2476 if op not in opt2revset:
2474 if op not in opt2revset:
2477 continue
2475 continue
2478 revop, andor = opt2revset[op]
2476 revop, andor = opt2revset[op]
2479 if '%(val)' not in revop:
2477 if '%(val)' not in revop:
2480 expr.append(revop)
2478 expr.append(revop)
2481 else:
2479 else:
2482 if not isinstance(val, list):
2480 if not isinstance(val, list):
2483 e = revop % {'val': val}
2481 e = revop % {'val': val}
2484 else:
2482 else:
2485 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2483 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2486 expr.append(e)
2484 expr.append(e)
2487
2485
2488 if expr:
2486 if expr:
2489 expr = '(' + ' and '.join(expr) + ')'
2487 expr = '(' + ' and '.join(expr) + ')'
2490 else:
2488 else:
2491 expr = None
2489 expr = None
2492 return expr, filematcher
2490 return expr, filematcher
2493
2491
2494 def _logrevs(repo, opts):
2492 def _logrevs(repo, opts):
2495 # Default --rev value depends on --follow but --follow behavior
2493 # Default --rev value depends on --follow but --follow behavior
2496 # depends on revisions resolved from --rev...
2494 # depends on revisions resolved from --rev...
2497 follow = opts.get('follow') or opts.get('follow_first')
2495 follow = opts.get('follow') or opts.get('follow_first')
2498 if opts.get('rev'):
2496 if opts.get('rev'):
2499 revs = scmutil.revrange(repo, opts['rev'])
2497 revs = scmutil.revrange(repo, opts['rev'])
2500 elif follow and repo.dirstate.p1() == nullid:
2498 elif follow and repo.dirstate.p1() == nullid:
2501 revs = smartset.baseset()
2499 revs = smartset.baseset()
2502 elif follow:
2500 elif follow:
2503 revs = repo.revs('reverse(:.)')
2501 revs = repo.revs('reverse(:.)')
2504 else:
2502 else:
2505 revs = smartset.spanset(repo)
2503 revs = smartset.spanset(repo)
2506 revs.reverse()
2504 revs.reverse()
2507 return revs
2505 return revs
2508
2506
2509 def getlogrevs(repo, pats, opts):
2507 def getlogrevs(repo, pats, opts):
2510 """Return (revs, expr, filematcher) where revs is an iterable of
2508 """Return (revs, expr, filematcher) where revs is an iterable of
2511 revision numbers, expr is a revset string built from log options
2509 revision numbers, expr is a revset string built from log options
2512 and file patterns or None, and used to filter 'revs'. If --stat or
2510 and file patterns or None, and used to filter 'revs'. If --stat or
2513 --patch are not passed filematcher is None. Otherwise it is a
2511 --patch are not passed filematcher is None. Otherwise it is a
2514 callable taking a revision number and returning a match objects
2512 callable taking a revision number and returning a match objects
2515 filtering the files to be detailed when displaying the revision.
2513 filtering the files to be detailed when displaying the revision.
2516 """
2514 """
2517 limit = loglimit(opts)
2515 limit = loglimit(opts)
2518 revs = _logrevs(repo, opts)
2516 revs = _logrevs(repo, opts)
2519 if not revs:
2517 if not revs:
2520 return smartset.baseset(), None, None
2518 return smartset.baseset(), None, None
2521 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2519 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2522 if opts.get('graph') and opts.get('rev'):
2520 if opts.get('graph') and opts.get('rev'):
2523 # User-specified revs might be unsorted, but don't sort before
2521 # User-specified revs might be unsorted, but don't sort before
2524 # _makelogrevset because it might depend on the order of revs
2522 # _makelogrevset because it might depend on the order of revs
2525 if not (revs.isdescending() or revs.istopo()):
2523 if not (revs.isdescending() or revs.istopo()):
2526 revs.sort(reverse=True)
2524 revs.sort(reverse=True)
2527 if expr:
2525 if expr:
2528 matcher = revset.match(repo.ui, expr)
2526 matcher = revset.match(repo.ui, expr)
2529 revs = matcher(repo, revs)
2527 revs = matcher(repo, revs)
2530 if limit is not None:
2528 if limit is not None:
2531 revs = revs.slice(0, limit)
2529 revs = revs.slice(0, limit)
2532 return revs, expr, filematcher
2530 return revs, expr, filematcher
2533
2531
2534 def _parselinerangelogopt(repo, opts):
2532 def _parselinerangelogopt(repo, opts):
2535 """Parse --line-range log option and return a list of tuples (filename,
2533 """Parse --line-range log option and return a list of tuples (filename,
2536 (fromline, toline)).
2534 (fromline, toline)).
2537 """
2535 """
2538 linerangebyfname = []
2536 linerangebyfname = []
2539 for pat in opts.get('line_range', []):
2537 for pat in opts.get('line_range', []):
2540 try:
2538 try:
2541 pat, linerange = pat.rsplit(',', 1)
2539 pat, linerange = pat.rsplit(',', 1)
2542 except ValueError:
2540 except ValueError:
2543 raise error.Abort(_('malformatted line-range pattern %s') % pat)
2541 raise error.Abort(_('malformatted line-range pattern %s') % pat)
2544 try:
2542 try:
2545 fromline, toline = map(int, linerange.split(':'))
2543 fromline, toline = map(int, linerange.split(':'))
2546 except ValueError:
2544 except ValueError:
2547 raise error.Abort(_("invalid line range for %s") % pat)
2545 raise error.Abort(_("invalid line range for %s") % pat)
2548 msg = _("line range pattern '%s' must match exactly one file") % pat
2546 msg = _("line range pattern '%s' must match exactly one file") % pat
2549 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
2547 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
2550 linerangebyfname.append(
2548 linerangebyfname.append(
2551 (fname, util.processlinerange(fromline, toline)))
2549 (fname, util.processlinerange(fromline, toline)))
2552 return linerangebyfname
2550 return linerangebyfname
2553
2551
2554 def getloglinerangerevs(repo, userrevs, opts):
2552 def getloglinerangerevs(repo, userrevs, opts):
2555 """Return (revs, filematcher, hunksfilter).
2553 """Return (revs, filematcher, hunksfilter).
2556
2554
2557 "revs" are revisions obtained by processing "line-range" log options and
2555 "revs" are revisions obtained by processing "line-range" log options and
2558 walking block ancestors of each specified file/line-range.
2556 walking block ancestors of each specified file/line-range.
2559
2557
2560 "filematcher(rev) -> match" is a factory function returning a match object
2558 "filematcher(rev) -> match" is a factory function returning a match object
2561 for a given revision for file patterns specified in --line-range option.
2559 for a given revision for file patterns specified in --line-range option.
2562 If neither --stat nor --patch options are passed, "filematcher" is None.
2560 If neither --stat nor --patch options are passed, "filematcher" is None.
2563
2561
2564 "hunksfilter(rev) -> filterfn(fctx, hunks)" is a factory function
2562 "hunksfilter(rev) -> filterfn(fctx, hunks)" is a factory function
2565 returning a hunks filtering function.
2563 returning a hunks filtering function.
2566 If neither --stat nor --patch options are passed, "filterhunks" is None.
2564 If neither --stat nor --patch options are passed, "filterhunks" is None.
2567 """
2565 """
2568 wctx = repo[None]
2566 wctx = repo[None]
2569
2567
2570 # Two-levels map of "rev -> file ctx -> [line range]".
2568 # Two-levels map of "rev -> file ctx -> [line range]".
2571 linerangesbyrev = {}
2569 linerangesbyrev = {}
2572 for fname, (fromline, toline) in _parselinerangelogopt(repo, opts):
2570 for fname, (fromline, toline) in _parselinerangelogopt(repo, opts):
2573 if fname not in wctx:
2571 if fname not in wctx:
2574 raise error.Abort(_('cannot follow file not in parent '
2572 raise error.Abort(_('cannot follow file not in parent '
2575 'revision: "%s"') % fname)
2573 'revision: "%s"') % fname)
2576 fctx = wctx.filectx(fname)
2574 fctx = wctx.filectx(fname)
2577 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
2575 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
2578 rev = fctx.introrev()
2576 rev = fctx.introrev()
2579 if rev not in userrevs:
2577 if rev not in userrevs:
2580 continue
2578 continue
2581 linerangesbyrev.setdefault(
2579 linerangesbyrev.setdefault(
2582 rev, {}).setdefault(
2580 rev, {}).setdefault(
2583 fctx.path(), []).append(linerange)
2581 fctx.path(), []).append(linerange)
2584
2582
2585 filematcher = None
2583 filematcher = None
2586 hunksfilter = None
2584 hunksfilter = None
2587 if opts.get('patch') or opts.get('stat'):
2585 if opts.get('patch') or opts.get('stat'):
2588
2586
2589 def nofilterhunksfn(fctx, hunks):
2587 def nofilterhunksfn(fctx, hunks):
2590 return hunks
2588 return hunks
2591
2589
2592 def hunksfilter(rev):
2590 def hunksfilter(rev):
2593 fctxlineranges = linerangesbyrev.get(rev)
2591 fctxlineranges = linerangesbyrev.get(rev)
2594 if fctxlineranges is None:
2592 if fctxlineranges is None:
2595 return nofilterhunksfn
2593 return nofilterhunksfn
2596
2594
2597 def filterfn(fctx, hunks):
2595 def filterfn(fctx, hunks):
2598 lineranges = fctxlineranges.get(fctx.path())
2596 lineranges = fctxlineranges.get(fctx.path())
2599 if lineranges is not None:
2597 if lineranges is not None:
2600 for hr, lines in hunks:
2598 for hr, lines in hunks:
2601 if hr is None: # binary
2599 if hr is None: # binary
2602 yield hr, lines
2600 yield hr, lines
2603 continue
2601 continue
2604 if any(mdiff.hunkinrange(hr[2:], lr)
2602 if any(mdiff.hunkinrange(hr[2:], lr)
2605 for lr in lineranges):
2603 for lr in lineranges):
2606 yield hr, lines
2604 yield hr, lines
2607 else:
2605 else:
2608 for hunk in hunks:
2606 for hunk in hunks:
2609 yield hunk
2607 yield hunk
2610
2608
2611 return filterfn
2609 return filterfn
2612
2610
2613 def filematcher(rev):
2611 def filematcher(rev):
2614 files = list(linerangesbyrev.get(rev, []))
2612 files = list(linerangesbyrev.get(rev, []))
2615 return scmutil.matchfiles(repo, files)
2613 return scmutil.matchfiles(repo, files)
2616
2614
2617 revs = sorted(linerangesbyrev, reverse=True)
2615 revs = sorted(linerangesbyrev, reverse=True)
2618
2616
2619 return revs, filematcher, hunksfilter
2617 return revs, filematcher, hunksfilter
2620
2618
2621 def _graphnodeformatter(ui, displayer):
2619 def _graphnodeformatter(ui, displayer):
2622 spec = ui.config('ui', 'graphnodetemplate')
2620 spec = ui.config('ui', 'graphnodetemplate')
2623 if not spec:
2621 if not spec:
2624 return templatekw.showgraphnode # fast path for "{graphnode}"
2622 return templatekw.showgraphnode # fast path for "{graphnode}"
2625
2623
2626 spec = templater.unquotestring(spec)
2624 spec = templater.unquotestring(spec)
2627 tres = formatter.templateresources(ui)
2625 tres = formatter.templateresources(ui)
2628 if isinstance(displayer, changeset_templater):
2626 if isinstance(displayer, changeset_templater):
2629 tres['cache'] = displayer.cache # reuse cache of slow templates
2627 tres['cache'] = displayer.cache # reuse cache of slow templates
2630 templ = formatter.maketemplater(ui, spec, defaults=templatekw.keywords,
2628 templ = formatter.maketemplater(ui, spec, defaults=templatekw.keywords,
2631 resources=tres)
2629 resources=tres)
2632 def formatnode(repo, ctx):
2630 def formatnode(repo, ctx):
2633 props = {'ctx': ctx, 'repo': repo, 'revcache': {}}
2631 props = {'ctx': ctx, 'repo': repo, 'revcache': {}}
2634 return templ.render(props)
2632 return templ.render(props)
2635 return formatnode
2633 return formatnode
2636
2634
2637 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2635 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2638 filematcher=None, props=None):
2636 filematcher=None, props=None):
2639 props = props or {}
2637 props = props or {}
2640 formatnode = _graphnodeformatter(ui, displayer)
2638 formatnode = _graphnodeformatter(ui, displayer)
2641 state = graphmod.asciistate()
2639 state = graphmod.asciistate()
2642 styles = state['styles']
2640 styles = state['styles']
2643
2641
2644 # only set graph styling if HGPLAIN is not set.
2642 # only set graph styling if HGPLAIN is not set.
2645 if ui.plain('graph'):
2643 if ui.plain('graph'):
2646 # set all edge styles to |, the default pre-3.8 behaviour
2644 # set all edge styles to |, the default pre-3.8 behaviour
2647 styles.update(dict.fromkeys(styles, '|'))
2645 styles.update(dict.fromkeys(styles, '|'))
2648 else:
2646 else:
2649 edgetypes = {
2647 edgetypes = {
2650 'parent': graphmod.PARENT,
2648 'parent': graphmod.PARENT,
2651 'grandparent': graphmod.GRANDPARENT,
2649 'grandparent': graphmod.GRANDPARENT,
2652 'missing': graphmod.MISSINGPARENT
2650 'missing': graphmod.MISSINGPARENT
2653 }
2651 }
2654 for name, key in edgetypes.items():
2652 for name, key in edgetypes.items():
2655 # experimental config: experimental.graphstyle.*
2653 # experimental config: experimental.graphstyle.*
2656 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2654 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2657 styles[key])
2655 styles[key])
2658 if not styles[key]:
2656 if not styles[key]:
2659 styles[key] = None
2657 styles[key] = None
2660
2658
2661 # experimental config: experimental.graphshorten
2659 # experimental config: experimental.graphshorten
2662 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2660 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2663
2661
2664 for rev, type, ctx, parents in dag:
2662 for rev, type, ctx, parents in dag:
2665 char = formatnode(repo, ctx)
2663 char = formatnode(repo, ctx)
2666 copies = None
2664 copies = None
2667 if getrenamed and ctx.rev():
2665 if getrenamed and ctx.rev():
2668 copies = []
2666 copies = []
2669 for fn in ctx.files():
2667 for fn in ctx.files():
2670 rename = getrenamed(fn, ctx.rev())
2668 rename = getrenamed(fn, ctx.rev())
2671 if rename:
2669 if rename:
2672 copies.append((fn, rename[0]))
2670 copies.append((fn, rename[0]))
2673 revmatchfn = None
2671 revmatchfn = None
2674 if filematcher is not None:
2672 if filematcher is not None:
2675 revmatchfn = filematcher(ctx.rev())
2673 revmatchfn = filematcher(ctx.rev())
2676 edges = edgefn(type, char, state, rev, parents)
2674 edges = edgefn(type, char, state, rev, parents)
2677 firstedge = next(edges)
2675 firstedge = next(edges)
2678 width = firstedge[2]
2676 width = firstedge[2]
2679 displayer.show(ctx, copies=copies, matchfn=revmatchfn,
2677 displayer.show(ctx, copies=copies, matchfn=revmatchfn,
2680 _graphwidth=width, **pycompat.strkwargs(props))
2678 _graphwidth=width, **pycompat.strkwargs(props))
2681 lines = displayer.hunk.pop(rev).split('\n')
2679 lines = displayer.hunk.pop(rev).split('\n')
2682 if not lines[-1]:
2680 if not lines[-1]:
2683 del lines[-1]
2681 del lines[-1]
2684 displayer.flush(ctx)
2682 displayer.flush(ctx)
2685 for type, char, width, coldata in itertools.chain([firstedge], edges):
2683 for type, char, width, coldata in itertools.chain([firstedge], edges):
2686 graphmod.ascii(ui, state, type, char, lines, coldata)
2684 graphmod.ascii(ui, state, type, char, lines, coldata)
2687 lines = []
2685 lines = []
2688 displayer.close()
2686 displayer.close()
2689
2687
2690 def graphlog(ui, repo, revs, filematcher, opts):
2688 def graphlog(ui, repo, revs, filematcher, opts):
2691 # Parameters are identical to log command ones
2689 # Parameters are identical to log command ones
2692 revdag = graphmod.dagwalker(repo, revs)
2690 revdag = graphmod.dagwalker(repo, revs)
2693
2691
2694 getrenamed = None
2692 getrenamed = None
2695 if opts.get('copies'):
2693 if opts.get('copies'):
2696 endrev = None
2694 endrev = None
2697 if opts.get('rev'):
2695 if opts.get('rev'):
2698 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2696 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2699 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2697 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2700
2698
2701 ui.pager('log')
2699 ui.pager('log')
2702 displayer = show_changeset(ui, repo, opts, buffered=True)
2700 displayer = show_changeset(ui, repo, opts, buffered=True)
2703 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2701 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2704 filematcher)
2702 filematcher)
2705
2703
2706 def checkunsupportedgraphflags(pats, opts):
2704 def checkunsupportedgraphflags(pats, opts):
2707 for op in ["newest_first"]:
2705 for op in ["newest_first"]:
2708 if op in opts and opts[op]:
2706 if op in opts and opts[op]:
2709 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2707 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2710 % op.replace("_", "-"))
2708 % op.replace("_", "-"))
2711
2709
2712 def graphrevs(repo, nodes, opts):
2710 def graphrevs(repo, nodes, opts):
2713 limit = loglimit(opts)
2711 limit = loglimit(opts)
2714 nodes.reverse()
2712 nodes.reverse()
2715 if limit is not None:
2713 if limit is not None:
2716 nodes = nodes[:limit]
2714 nodes = nodes[:limit]
2717 return graphmod.nodes(repo, nodes)
2715 return graphmod.nodes(repo, nodes)
2718
2716
2719 def add(ui, repo, match, prefix, explicitonly, **opts):
2717 def add(ui, repo, match, prefix, explicitonly, **opts):
2720 join = lambda f: os.path.join(prefix, f)
2718 join = lambda f: os.path.join(prefix, f)
2721 bad = []
2719 bad = []
2722
2720
2723 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2721 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2724 names = []
2722 names = []
2725 wctx = repo[None]
2723 wctx = repo[None]
2726 cca = None
2724 cca = None
2727 abort, warn = scmutil.checkportabilityalert(ui)
2725 abort, warn = scmutil.checkportabilityalert(ui)
2728 if abort or warn:
2726 if abort or warn:
2729 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2727 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2730
2728
2731 badmatch = matchmod.badmatch(match, badfn)
2729 badmatch = matchmod.badmatch(match, badfn)
2732 dirstate = repo.dirstate
2730 dirstate = repo.dirstate
2733 # We don't want to just call wctx.walk here, since it would return a lot of
2731 # We don't want to just call wctx.walk here, since it would return a lot of
2734 # clean files, which we aren't interested in and takes time.
2732 # clean files, which we aren't interested in and takes time.
2735 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
2733 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
2736 unknown=True, ignored=False, full=False)):
2734 unknown=True, ignored=False, full=False)):
2737 exact = match.exact(f)
2735 exact = match.exact(f)
2738 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2736 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2739 if cca:
2737 if cca:
2740 cca(f)
2738 cca(f)
2741 names.append(f)
2739 names.append(f)
2742 if ui.verbose or not exact:
2740 if ui.verbose or not exact:
2743 ui.status(_('adding %s\n') % match.rel(f))
2741 ui.status(_('adding %s\n') % match.rel(f))
2744
2742
2745 for subpath in sorted(wctx.substate):
2743 for subpath in sorted(wctx.substate):
2746 sub = wctx.sub(subpath)
2744 sub = wctx.sub(subpath)
2747 try:
2745 try:
2748 submatch = matchmod.subdirmatcher(subpath, match)
2746 submatch = matchmod.subdirmatcher(subpath, match)
2749 if opts.get(r'subrepos'):
2747 if opts.get(r'subrepos'):
2750 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2748 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2751 else:
2749 else:
2752 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2750 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2753 except error.LookupError:
2751 except error.LookupError:
2754 ui.status(_("skipping missing subrepository: %s\n")
2752 ui.status(_("skipping missing subrepository: %s\n")
2755 % join(subpath))
2753 % join(subpath))
2756
2754
2757 if not opts.get(r'dry_run'):
2755 if not opts.get(r'dry_run'):
2758 rejected = wctx.add(names, prefix)
2756 rejected = wctx.add(names, prefix)
2759 bad.extend(f for f in rejected if f in match.files())
2757 bad.extend(f for f in rejected if f in match.files())
2760 return bad
2758 return bad
2761
2759
2762 def addwebdirpath(repo, serverpath, webconf):
2760 def addwebdirpath(repo, serverpath, webconf):
2763 webconf[serverpath] = repo.root
2761 webconf[serverpath] = repo.root
2764 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2762 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2765
2763
2766 for r in repo.revs('filelog("path:.hgsub")'):
2764 for r in repo.revs('filelog("path:.hgsub")'):
2767 ctx = repo[r]
2765 ctx = repo[r]
2768 for subpath in ctx.substate:
2766 for subpath in ctx.substate:
2769 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2767 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2770
2768
2771 def forget(ui, repo, match, prefix, explicitonly):
2769 def forget(ui, repo, match, prefix, explicitonly):
2772 join = lambda f: os.path.join(prefix, f)
2770 join = lambda f: os.path.join(prefix, f)
2773 bad = []
2771 bad = []
2774 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2772 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2775 wctx = repo[None]
2773 wctx = repo[None]
2776 forgot = []
2774 forgot = []
2777
2775
2778 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2776 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2779 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2777 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2780 if explicitonly:
2778 if explicitonly:
2781 forget = [f for f in forget if match.exact(f)]
2779 forget = [f for f in forget if match.exact(f)]
2782
2780
2783 for subpath in sorted(wctx.substate):
2781 for subpath in sorted(wctx.substate):
2784 sub = wctx.sub(subpath)
2782 sub = wctx.sub(subpath)
2785 try:
2783 try:
2786 submatch = matchmod.subdirmatcher(subpath, match)
2784 submatch = matchmod.subdirmatcher(subpath, match)
2787 subbad, subforgot = sub.forget(submatch, prefix)
2785 subbad, subforgot = sub.forget(submatch, prefix)
2788 bad.extend([subpath + '/' + f for f in subbad])
2786 bad.extend([subpath + '/' + f for f in subbad])
2789 forgot.extend([subpath + '/' + f for f in subforgot])
2787 forgot.extend([subpath + '/' + f for f in subforgot])
2790 except error.LookupError:
2788 except error.LookupError:
2791 ui.status(_("skipping missing subrepository: %s\n")
2789 ui.status(_("skipping missing subrepository: %s\n")
2792 % join(subpath))
2790 % join(subpath))
2793
2791
2794 if not explicitonly:
2792 if not explicitonly:
2795 for f in match.files():
2793 for f in match.files():
2796 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2794 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2797 if f not in forgot:
2795 if f not in forgot:
2798 if repo.wvfs.exists(f):
2796 if repo.wvfs.exists(f):
2799 # Don't complain if the exact case match wasn't given.
2797 # Don't complain if the exact case match wasn't given.
2800 # But don't do this until after checking 'forgot', so
2798 # But don't do this until after checking 'forgot', so
2801 # that subrepo files aren't normalized, and this op is
2799 # that subrepo files aren't normalized, and this op is
2802 # purely from data cached by the status walk above.
2800 # purely from data cached by the status walk above.
2803 if repo.dirstate.normalize(f) in repo.dirstate:
2801 if repo.dirstate.normalize(f) in repo.dirstate:
2804 continue
2802 continue
2805 ui.warn(_('not removing %s: '
2803 ui.warn(_('not removing %s: '
2806 'file is already untracked\n')
2804 'file is already untracked\n')
2807 % match.rel(f))
2805 % match.rel(f))
2808 bad.append(f)
2806 bad.append(f)
2809
2807
2810 for f in forget:
2808 for f in forget:
2811 if ui.verbose or not match.exact(f):
2809 if ui.verbose or not match.exact(f):
2812 ui.status(_('removing %s\n') % match.rel(f))
2810 ui.status(_('removing %s\n') % match.rel(f))
2813
2811
2814 rejected = wctx.forget(forget, prefix)
2812 rejected = wctx.forget(forget, prefix)
2815 bad.extend(f for f in rejected if f in match.files())
2813 bad.extend(f for f in rejected if f in match.files())
2816 forgot.extend(f for f in forget if f not in rejected)
2814 forgot.extend(f for f in forget if f not in rejected)
2817 return bad, forgot
2815 return bad, forgot
2818
2816
2819 def files(ui, ctx, m, fm, fmt, subrepos):
2817 def files(ui, ctx, m, fm, fmt, subrepos):
2820 rev = ctx.rev()
2818 rev = ctx.rev()
2821 ret = 1
2819 ret = 1
2822 ds = ctx.repo().dirstate
2820 ds = ctx.repo().dirstate
2823
2821
2824 for f in ctx.matches(m):
2822 for f in ctx.matches(m):
2825 if rev is None and ds[f] == 'r':
2823 if rev is None and ds[f] == 'r':
2826 continue
2824 continue
2827 fm.startitem()
2825 fm.startitem()
2828 if ui.verbose:
2826 if ui.verbose:
2829 fc = ctx[f]
2827 fc = ctx[f]
2830 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2828 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2831 fm.data(abspath=f)
2829 fm.data(abspath=f)
2832 fm.write('path', fmt, m.rel(f))
2830 fm.write('path', fmt, m.rel(f))
2833 ret = 0
2831 ret = 0
2834
2832
2835 for subpath in sorted(ctx.substate):
2833 for subpath in sorted(ctx.substate):
2836 submatch = matchmod.subdirmatcher(subpath, m)
2834 submatch = matchmod.subdirmatcher(subpath, m)
2837 if (subrepos or m.exact(subpath) or any(submatch.files())):
2835 if (subrepos or m.exact(subpath) or any(submatch.files())):
2838 sub = ctx.sub(subpath)
2836 sub = ctx.sub(subpath)
2839 try:
2837 try:
2840 recurse = m.exact(subpath) or subrepos
2838 recurse = m.exact(subpath) or subrepos
2841 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2839 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2842 ret = 0
2840 ret = 0
2843 except error.LookupError:
2841 except error.LookupError:
2844 ui.status(_("skipping missing subrepository: %s\n")
2842 ui.status(_("skipping missing subrepository: %s\n")
2845 % m.abs(subpath))
2843 % m.abs(subpath))
2846
2844
2847 return ret
2845 return ret
2848
2846
2849 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2847 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2850 join = lambda f: os.path.join(prefix, f)
2848 join = lambda f: os.path.join(prefix, f)
2851 ret = 0
2849 ret = 0
2852 s = repo.status(match=m, clean=True)
2850 s = repo.status(match=m, clean=True)
2853 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2851 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2854
2852
2855 wctx = repo[None]
2853 wctx = repo[None]
2856
2854
2857 if warnings is None:
2855 if warnings is None:
2858 warnings = []
2856 warnings = []
2859 warn = True
2857 warn = True
2860 else:
2858 else:
2861 warn = False
2859 warn = False
2862
2860
2863 subs = sorted(wctx.substate)
2861 subs = sorted(wctx.substate)
2864 total = len(subs)
2862 total = len(subs)
2865 count = 0
2863 count = 0
2866 for subpath in subs:
2864 for subpath in subs:
2867 count += 1
2865 count += 1
2868 submatch = matchmod.subdirmatcher(subpath, m)
2866 submatch = matchmod.subdirmatcher(subpath, m)
2869 if subrepos or m.exact(subpath) or any(submatch.files()):
2867 if subrepos or m.exact(subpath) or any(submatch.files()):
2870 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2868 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2871 sub = wctx.sub(subpath)
2869 sub = wctx.sub(subpath)
2872 try:
2870 try:
2873 if sub.removefiles(submatch, prefix, after, force, subrepos,
2871 if sub.removefiles(submatch, prefix, after, force, subrepos,
2874 warnings):
2872 warnings):
2875 ret = 1
2873 ret = 1
2876 except error.LookupError:
2874 except error.LookupError:
2877 warnings.append(_("skipping missing subrepository: %s\n")
2875 warnings.append(_("skipping missing subrepository: %s\n")
2878 % join(subpath))
2876 % join(subpath))
2879 ui.progress(_('searching'), None)
2877 ui.progress(_('searching'), None)
2880
2878
2881 # warn about failure to delete explicit files/dirs
2879 # warn about failure to delete explicit files/dirs
2882 deleteddirs = util.dirs(deleted)
2880 deleteddirs = util.dirs(deleted)
2883 files = m.files()
2881 files = m.files()
2884 total = len(files)
2882 total = len(files)
2885 count = 0
2883 count = 0
2886 for f in files:
2884 for f in files:
2887 def insubrepo():
2885 def insubrepo():
2888 for subpath in wctx.substate:
2886 for subpath in wctx.substate:
2889 if f.startswith(subpath + '/'):
2887 if f.startswith(subpath + '/'):
2890 return True
2888 return True
2891 return False
2889 return False
2892
2890
2893 count += 1
2891 count += 1
2894 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2892 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2895 isdir = f in deleteddirs or wctx.hasdir(f)
2893 isdir = f in deleteddirs or wctx.hasdir(f)
2896 if (f in repo.dirstate or isdir or f == '.'
2894 if (f in repo.dirstate or isdir or f == '.'
2897 or insubrepo() or f in subs):
2895 or insubrepo() or f in subs):
2898 continue
2896 continue
2899
2897
2900 if repo.wvfs.exists(f):
2898 if repo.wvfs.exists(f):
2901 if repo.wvfs.isdir(f):
2899 if repo.wvfs.isdir(f):
2902 warnings.append(_('not removing %s: no tracked files\n')
2900 warnings.append(_('not removing %s: no tracked files\n')
2903 % m.rel(f))
2901 % m.rel(f))
2904 else:
2902 else:
2905 warnings.append(_('not removing %s: file is untracked\n')
2903 warnings.append(_('not removing %s: file is untracked\n')
2906 % m.rel(f))
2904 % m.rel(f))
2907 # missing files will generate a warning elsewhere
2905 # missing files will generate a warning elsewhere
2908 ret = 1
2906 ret = 1
2909 ui.progress(_('deleting'), None)
2907 ui.progress(_('deleting'), None)
2910
2908
2911 if force:
2909 if force:
2912 list = modified + deleted + clean + added
2910 list = modified + deleted + clean + added
2913 elif after:
2911 elif after:
2914 list = deleted
2912 list = deleted
2915 remaining = modified + added + clean
2913 remaining = modified + added + clean
2916 total = len(remaining)
2914 total = len(remaining)
2917 count = 0
2915 count = 0
2918 for f in remaining:
2916 for f in remaining:
2919 count += 1
2917 count += 1
2920 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2918 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2921 if ui.verbose or (f in files):
2919 if ui.verbose or (f in files):
2922 warnings.append(_('not removing %s: file still exists\n')
2920 warnings.append(_('not removing %s: file still exists\n')
2923 % m.rel(f))
2921 % m.rel(f))
2924 ret = 1
2922 ret = 1
2925 ui.progress(_('skipping'), None)
2923 ui.progress(_('skipping'), None)
2926 else:
2924 else:
2927 list = deleted + clean
2925 list = deleted + clean
2928 total = len(modified) + len(added)
2926 total = len(modified) + len(added)
2929 count = 0
2927 count = 0
2930 for f in modified:
2928 for f in modified:
2931 count += 1
2929 count += 1
2932 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2930 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2933 warnings.append(_('not removing %s: file is modified (use -f'
2931 warnings.append(_('not removing %s: file is modified (use -f'
2934 ' to force removal)\n') % m.rel(f))
2932 ' to force removal)\n') % m.rel(f))
2935 ret = 1
2933 ret = 1
2936 for f in added:
2934 for f in added:
2937 count += 1
2935 count += 1
2938 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2936 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2939 warnings.append(_("not removing %s: file has been marked for add"
2937 warnings.append(_("not removing %s: file has been marked for add"
2940 " (use 'hg forget' to undo add)\n") % m.rel(f))
2938 " (use 'hg forget' to undo add)\n") % m.rel(f))
2941 ret = 1
2939 ret = 1
2942 ui.progress(_('skipping'), None)
2940 ui.progress(_('skipping'), None)
2943
2941
2944 list = sorted(list)
2942 list = sorted(list)
2945 total = len(list)
2943 total = len(list)
2946 count = 0
2944 count = 0
2947 for f in list:
2945 for f in list:
2948 count += 1
2946 count += 1
2949 if ui.verbose or not m.exact(f):
2947 if ui.verbose or not m.exact(f):
2950 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2948 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2951 ui.status(_('removing %s\n') % m.rel(f))
2949 ui.status(_('removing %s\n') % m.rel(f))
2952 ui.progress(_('deleting'), None)
2950 ui.progress(_('deleting'), None)
2953
2951
2954 with repo.wlock():
2952 with repo.wlock():
2955 if not after:
2953 if not after:
2956 for f in list:
2954 for f in list:
2957 if f in added:
2955 if f in added:
2958 continue # we never unlink added files on remove
2956 continue # we never unlink added files on remove
2959 repo.wvfs.unlinkpath(f, ignoremissing=True)
2957 repo.wvfs.unlinkpath(f, ignoremissing=True)
2960 repo[None].forget(list)
2958 repo[None].forget(list)
2961
2959
2962 if warn:
2960 if warn:
2963 for warning in warnings:
2961 for warning in warnings:
2964 ui.warn(warning)
2962 ui.warn(warning)
2965
2963
2966 return ret
2964 return ret
2967
2965
2968 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2966 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2969 err = 1
2967 err = 1
2970 opts = pycompat.byteskwargs(opts)
2968 opts = pycompat.byteskwargs(opts)
2971
2969
2972 def write(path):
2970 def write(path):
2973 filename = None
2971 filename = None
2974 if fntemplate:
2972 if fntemplate:
2975 filename = makefilename(repo, fntemplate, ctx.node(),
2973 filename = makefilename(repo, fntemplate, ctx.node(),
2976 pathname=os.path.join(prefix, path))
2974 pathname=os.path.join(prefix, path))
2977 # attempt to create the directory if it does not already exist
2975 # attempt to create the directory if it does not already exist
2978 try:
2976 try:
2979 os.makedirs(os.path.dirname(filename))
2977 os.makedirs(os.path.dirname(filename))
2980 except OSError:
2978 except OSError:
2981 pass
2979 pass
2982 with formatter.maybereopen(basefm, filename, opts) as fm:
2980 with formatter.maybereopen(basefm, filename, opts) as fm:
2983 data = ctx[path].data()
2981 data = ctx[path].data()
2984 if opts.get('decode'):
2982 if opts.get('decode'):
2985 data = repo.wwritedata(path, data)
2983 data = repo.wwritedata(path, data)
2986 fm.startitem()
2984 fm.startitem()
2987 fm.write('data', '%s', data)
2985 fm.write('data', '%s', data)
2988 fm.data(abspath=path, path=matcher.rel(path))
2986 fm.data(abspath=path, path=matcher.rel(path))
2989
2987
2990 # Automation often uses hg cat on single files, so special case it
2988 # Automation often uses hg cat on single files, so special case it
2991 # for performance to avoid the cost of parsing the manifest.
2989 # for performance to avoid the cost of parsing the manifest.
2992 if len(matcher.files()) == 1 and not matcher.anypats():
2990 if len(matcher.files()) == 1 and not matcher.anypats():
2993 file = matcher.files()[0]
2991 file = matcher.files()[0]
2994 mfl = repo.manifestlog
2992 mfl = repo.manifestlog
2995 mfnode = ctx.manifestnode()
2993 mfnode = ctx.manifestnode()
2996 try:
2994 try:
2997 if mfnode and mfl[mfnode].find(file)[0]:
2995 if mfnode and mfl[mfnode].find(file)[0]:
2998 write(file)
2996 write(file)
2999 return 0
2997 return 0
3000 except KeyError:
2998 except KeyError:
3001 pass
2999 pass
3002
3000
3003 for abs in ctx.walk(matcher):
3001 for abs in ctx.walk(matcher):
3004 write(abs)
3002 write(abs)
3005 err = 0
3003 err = 0
3006
3004
3007 for subpath in sorted(ctx.substate):
3005 for subpath in sorted(ctx.substate):
3008 sub = ctx.sub(subpath)
3006 sub = ctx.sub(subpath)
3009 try:
3007 try:
3010 submatch = matchmod.subdirmatcher(subpath, matcher)
3008 submatch = matchmod.subdirmatcher(subpath, matcher)
3011
3009
3012 if not sub.cat(submatch, basefm, fntemplate,
3010 if not sub.cat(submatch, basefm, fntemplate,
3013 os.path.join(prefix, sub._path),
3011 os.path.join(prefix, sub._path),
3014 **pycompat.strkwargs(opts)):
3012 **pycompat.strkwargs(opts)):
3015 err = 0
3013 err = 0
3016 except error.RepoLookupError:
3014 except error.RepoLookupError:
3017 ui.status(_("skipping missing subrepository: %s\n")
3015 ui.status(_("skipping missing subrepository: %s\n")
3018 % os.path.join(prefix, subpath))
3016 % os.path.join(prefix, subpath))
3019
3017
3020 return err
3018 return err
3021
3019
3022 def commit(ui, repo, commitfunc, pats, opts):
3020 def commit(ui, repo, commitfunc, pats, opts):
3023 '''commit the specified files or all outstanding changes'''
3021 '''commit the specified files or all outstanding changes'''
3024 date = opts.get('date')
3022 date = opts.get('date')
3025 if date:
3023 if date:
3026 opts['date'] = util.parsedate(date)
3024 opts['date'] = util.parsedate(date)
3027 message = logmessage(ui, opts)
3025 message = logmessage(ui, opts)
3028 matcher = scmutil.match(repo[None], pats, opts)
3026 matcher = scmutil.match(repo[None], pats, opts)
3029
3027
3030 dsguard = None
3028 dsguard = None
3031 # extract addremove carefully -- this function can be called from a command
3029 # extract addremove carefully -- this function can be called from a command
3032 # that doesn't support addremove
3030 # that doesn't support addremove
3033 if opts.get('addremove'):
3031 if opts.get('addremove'):
3034 dsguard = dirstateguard.dirstateguard(repo, 'commit')
3032 dsguard = dirstateguard.dirstateguard(repo, 'commit')
3035 with dsguard or util.nullcontextmanager():
3033 with dsguard or util.nullcontextmanager():
3036 if dsguard:
3034 if dsguard:
3037 if scmutil.addremove(repo, matcher, "", opts) != 0:
3035 if scmutil.addremove(repo, matcher, "", opts) != 0:
3038 raise error.Abort(
3036 raise error.Abort(
3039 _("failed to mark all new/missing files as added/removed"))
3037 _("failed to mark all new/missing files as added/removed"))
3040
3038
3041 return commitfunc(ui, repo, message, matcher, opts)
3039 return commitfunc(ui, repo, message, matcher, opts)
3042
3040
3043 def samefile(f, ctx1, ctx2):
3041 def samefile(f, ctx1, ctx2):
3044 if f in ctx1.manifest():
3042 if f in ctx1.manifest():
3045 a = ctx1.filectx(f)
3043 a = ctx1.filectx(f)
3046 if f in ctx2.manifest():
3044 if f in ctx2.manifest():
3047 b = ctx2.filectx(f)
3045 b = ctx2.filectx(f)
3048 return (not a.cmp(b)
3046 return (not a.cmp(b)
3049 and a.flags() == b.flags())
3047 and a.flags() == b.flags())
3050 else:
3048 else:
3051 return False
3049 return False
3052 else:
3050 else:
3053 return f not in ctx2.manifest()
3051 return f not in ctx2.manifest()
3054
3052
3055 def amend(ui, repo, old, extra, pats, opts):
3053 def amend(ui, repo, old, extra, pats, opts):
3056 # avoid cycle context -> subrepo -> cmdutil
3054 # avoid cycle context -> subrepo -> cmdutil
3057 from . import context
3055 from . import context
3058
3056
3059 # amend will reuse the existing user if not specified, but the obsolete
3057 # amend will reuse the existing user if not specified, but the obsolete
3060 # marker creation requires that the current user's name is specified.
3058 # marker creation requires that the current user's name is specified.
3061 if obsolete.isenabled(repo, obsolete.createmarkersopt):
3059 if obsolete.isenabled(repo, obsolete.createmarkersopt):
3062 ui.username() # raise exception if username not set
3060 ui.username() # raise exception if username not set
3063
3061
3064 ui.note(_('amending changeset %s\n') % old)
3062 ui.note(_('amending changeset %s\n') % old)
3065 base = old.p1()
3063 base = old.p1()
3066
3064
3067 with repo.wlock(), repo.lock(), repo.transaction('amend'):
3065 with repo.wlock(), repo.lock(), repo.transaction('amend'):
3068 # Participating changesets:
3066 # Participating changesets:
3069 #
3067 #
3070 # wctx o - workingctx that contains changes from working copy
3068 # wctx o - workingctx that contains changes from working copy
3071 # | to go into amending commit
3069 # | to go into amending commit
3072 # |
3070 # |
3073 # old o - changeset to amend
3071 # old o - changeset to amend
3074 # |
3072 # |
3075 # base o - first parent of the changeset to amend
3073 # base o - first parent of the changeset to amend
3076 wctx = repo[None]
3074 wctx = repo[None]
3077
3075
3078 # Copy to avoid mutating input
3076 # Copy to avoid mutating input
3079 extra = extra.copy()
3077 extra = extra.copy()
3080 # Update extra dict from amended commit (e.g. to preserve graft
3078 # Update extra dict from amended commit (e.g. to preserve graft
3081 # source)
3079 # source)
3082 extra.update(old.extra())
3080 extra.update(old.extra())
3083
3081
3084 # Also update it from the from the wctx
3082 # Also update it from the from the wctx
3085 extra.update(wctx.extra())
3083 extra.update(wctx.extra())
3086
3084
3087 user = opts.get('user') or old.user()
3085 user = opts.get('user') or old.user()
3088 date = opts.get('date') or old.date()
3086 date = opts.get('date') or old.date()
3089
3087
3090 # Parse the date to allow comparison between date and old.date()
3088 # Parse the date to allow comparison between date and old.date()
3091 date = util.parsedate(date)
3089 date = util.parsedate(date)
3092
3090
3093 if len(old.parents()) > 1:
3091 if len(old.parents()) > 1:
3094 # ctx.files() isn't reliable for merges, so fall back to the
3092 # ctx.files() isn't reliable for merges, so fall back to the
3095 # slower repo.status() method
3093 # slower repo.status() method
3096 files = set([fn for st in repo.status(base, old)[:3]
3094 files = set([fn for st in repo.status(base, old)[:3]
3097 for fn in st])
3095 for fn in st])
3098 else:
3096 else:
3099 files = set(old.files())
3097 files = set(old.files())
3100
3098
3101 # add/remove the files to the working copy if the "addremove" option
3099 # add/remove the files to the working copy if the "addremove" option
3102 # was specified.
3100 # was specified.
3103 matcher = scmutil.match(wctx, pats, opts)
3101 matcher = scmutil.match(wctx, pats, opts)
3104 if (opts.get('addremove')
3102 if (opts.get('addremove')
3105 and scmutil.addremove(repo, matcher, "", opts)):
3103 and scmutil.addremove(repo, matcher, "", opts)):
3106 raise error.Abort(
3104 raise error.Abort(
3107 _("failed to mark all new/missing files as added/removed"))
3105 _("failed to mark all new/missing files as added/removed"))
3108
3106
3109 # Check subrepos. This depends on in-place wctx._status update in
3107 # Check subrepos. This depends on in-place wctx._status update in
3110 # subrepo.precommit(). To minimize the risk of this hack, we do
3108 # subrepo.precommit(). To minimize the risk of this hack, we do
3111 # nothing if .hgsub does not exist.
3109 # nothing if .hgsub does not exist.
3112 if '.hgsub' in wctx or '.hgsub' in old:
3110 if '.hgsub' in wctx or '.hgsub' in old:
3113 from . import subrepo # avoid cycle: cmdutil -> subrepo -> cmdutil
3111 from . import subrepo # avoid cycle: cmdutil -> subrepo -> cmdutil
3114 subs, commitsubs, newsubstate = subrepo.precommit(
3112 subs, commitsubs, newsubstate = subrepo.precommit(
3115 ui, wctx, wctx._status, matcher)
3113 ui, wctx, wctx._status, matcher)
3116 # amend should abort if commitsubrepos is enabled
3114 # amend should abort if commitsubrepos is enabled
3117 assert not commitsubs
3115 assert not commitsubs
3118 if subs:
3116 if subs:
3119 subrepo.writestate(repo, newsubstate)
3117 subrepo.writestate(repo, newsubstate)
3120
3118
3121 filestoamend = set(f for f in wctx.files() if matcher(f))
3119 filestoamend = set(f for f in wctx.files() if matcher(f))
3122
3120
3123 changes = (len(filestoamend) > 0)
3121 changes = (len(filestoamend) > 0)
3124 if changes:
3122 if changes:
3125 # Recompute copies (avoid recording a -> b -> a)
3123 # Recompute copies (avoid recording a -> b -> a)
3126 copied = copies.pathcopies(base, wctx, matcher)
3124 copied = copies.pathcopies(base, wctx, matcher)
3127 if old.p2:
3125 if old.p2:
3128 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
3126 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
3129
3127
3130 # Prune files which were reverted by the updates: if old
3128 # Prune files which were reverted by the updates: if old
3131 # introduced file X and the file was renamed in the working
3129 # introduced file X and the file was renamed in the working
3132 # copy, then those two files are the same and
3130 # copy, then those two files are the same and
3133 # we can discard X from our list of files. Likewise if X
3131 # we can discard X from our list of files. Likewise if X
3134 # was removed, it's no longer relevant. If X is missing (aka
3132 # was removed, it's no longer relevant. If X is missing (aka
3135 # deleted), old X must be preserved.
3133 # deleted), old X must be preserved.
3136 files.update(filestoamend)
3134 files.update(filestoamend)
3137 files = [f for f in files if (not samefile(f, wctx, base)
3135 files = [f for f in files if (not samefile(f, wctx, base)
3138 or f in wctx.deleted())]
3136 or f in wctx.deleted())]
3139
3137
3140 def filectxfn(repo, ctx_, path):
3138 def filectxfn(repo, ctx_, path):
3141 try:
3139 try:
3142 # If the file being considered is not amongst the files
3140 # If the file being considered is not amongst the files
3143 # to be amended, we should return the file context from the
3141 # to be amended, we should return the file context from the
3144 # old changeset. This avoids issues when only some files in
3142 # old changeset. This avoids issues when only some files in
3145 # the working copy are being amended but there are also
3143 # the working copy are being amended but there are also
3146 # changes to other files from the old changeset.
3144 # changes to other files from the old changeset.
3147 if path not in filestoamend:
3145 if path not in filestoamend:
3148 return old.filectx(path)
3146 return old.filectx(path)
3149
3147
3150 # Return None for removed files.
3148 # Return None for removed files.
3151 if path in wctx.removed():
3149 if path in wctx.removed():
3152 return None
3150 return None
3153
3151
3154 fctx = wctx[path]
3152 fctx = wctx[path]
3155 flags = fctx.flags()
3153 flags = fctx.flags()
3156 mctx = context.memfilectx(repo, ctx_,
3154 mctx = context.memfilectx(repo, ctx_,
3157 fctx.path(), fctx.data(),
3155 fctx.path(), fctx.data(),
3158 islink='l' in flags,
3156 islink='l' in flags,
3159 isexec='x' in flags,
3157 isexec='x' in flags,
3160 copied=copied.get(path))
3158 copied=copied.get(path))
3161 return mctx
3159 return mctx
3162 except KeyError:
3160 except KeyError:
3163 return None
3161 return None
3164 else:
3162 else:
3165 ui.note(_('copying changeset %s to %s\n') % (old, base))
3163 ui.note(_('copying changeset %s to %s\n') % (old, base))
3166
3164
3167 # Use version of files as in the old cset
3165 # Use version of files as in the old cset
3168 def filectxfn(repo, ctx_, path):
3166 def filectxfn(repo, ctx_, path):
3169 try:
3167 try:
3170 return old.filectx(path)
3168 return old.filectx(path)
3171 except KeyError:
3169 except KeyError:
3172 return None
3170 return None
3173
3171
3174 # See if we got a message from -m or -l, if not, open the editor with
3172 # See if we got a message from -m or -l, if not, open the editor with
3175 # the message of the changeset to amend.
3173 # the message of the changeset to amend.
3176 message = logmessage(ui, opts)
3174 message = logmessage(ui, opts)
3177
3175
3178 editform = mergeeditform(old, 'commit.amend')
3176 editform = mergeeditform(old, 'commit.amend')
3179 editor = getcommiteditor(editform=editform,
3177 editor = getcommiteditor(editform=editform,
3180 **pycompat.strkwargs(opts))
3178 **pycompat.strkwargs(opts))
3181
3179
3182 if not message:
3180 if not message:
3183 editor = getcommiteditor(edit=True, editform=editform)
3181 editor = getcommiteditor(edit=True, editform=editform)
3184 message = old.description()
3182 message = old.description()
3185
3183
3186 pureextra = extra.copy()
3184 pureextra = extra.copy()
3187 extra['amend_source'] = old.hex()
3185 extra['amend_source'] = old.hex()
3188
3186
3189 new = context.memctx(repo,
3187 new = context.memctx(repo,
3190 parents=[base.node(), old.p2().node()],
3188 parents=[base.node(), old.p2().node()],
3191 text=message,
3189 text=message,
3192 files=files,
3190 files=files,
3193 filectxfn=filectxfn,
3191 filectxfn=filectxfn,
3194 user=user,
3192 user=user,
3195 date=date,
3193 date=date,
3196 extra=extra,
3194 extra=extra,
3197 editor=editor)
3195 editor=editor)
3198
3196
3199 newdesc = changelog.stripdesc(new.description())
3197 newdesc = changelog.stripdesc(new.description())
3200 if ((not changes)
3198 if ((not changes)
3201 and newdesc == old.description()
3199 and newdesc == old.description()
3202 and user == old.user()
3200 and user == old.user()
3203 and date == old.date()
3201 and date == old.date()
3204 and pureextra == old.extra()):
3202 and pureextra == old.extra()):
3205 # nothing changed. continuing here would create a new node
3203 # nothing changed. continuing here would create a new node
3206 # anyway because of the amend_source noise.
3204 # anyway because of the amend_source noise.
3207 #
3205 #
3208 # This not what we expect from amend.
3206 # This not what we expect from amend.
3209 return old.node()
3207 return old.node()
3210
3208
3211 if opts.get('secret'):
3209 if opts.get('secret'):
3212 commitphase = 'secret'
3210 commitphase = 'secret'
3213 else:
3211 else:
3214 commitphase = old.phase()
3212 commitphase = old.phase()
3215 overrides = {('phases', 'new-commit'): commitphase}
3213 overrides = {('phases', 'new-commit'): commitphase}
3216 with ui.configoverride(overrides, 'amend'):
3214 with ui.configoverride(overrides, 'amend'):
3217 newid = repo.commitctx(new)
3215 newid = repo.commitctx(new)
3218
3216
3219 # Reroute the working copy parent to the new changeset
3217 # Reroute the working copy parent to the new changeset
3220 repo.setparents(newid, nullid)
3218 repo.setparents(newid, nullid)
3221 mapping = {old.node(): (newid,)}
3219 mapping = {old.node(): (newid,)}
3222 obsmetadata = None
3220 obsmetadata = None
3223 if opts.get('note'):
3221 if opts.get('note'):
3224 obsmetadata = {'note': opts['note']}
3222 obsmetadata = {'note': opts['note']}
3225 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata)
3223 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata)
3226
3224
3227 # Fixing the dirstate because localrepo.commitctx does not update
3225 # Fixing the dirstate because localrepo.commitctx does not update
3228 # it. This is rather convenient because we did not need to update
3226 # it. This is rather convenient because we did not need to update
3229 # the dirstate for all the files in the new commit which commitctx
3227 # the dirstate for all the files in the new commit which commitctx
3230 # could have done if it updated the dirstate. Now, we can
3228 # could have done if it updated the dirstate. Now, we can
3231 # selectively update the dirstate only for the amended files.
3229 # selectively update the dirstate only for the amended files.
3232 dirstate = repo.dirstate
3230 dirstate = repo.dirstate
3233
3231
3234 # Update the state of the files which were added and
3232 # Update the state of the files which were added and
3235 # and modified in the amend to "normal" in the dirstate.
3233 # and modified in the amend to "normal" in the dirstate.
3236 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
3234 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
3237 for f in normalfiles:
3235 for f in normalfiles:
3238 dirstate.normal(f)
3236 dirstate.normal(f)
3239
3237
3240 # Update the state of files which were removed in the amend
3238 # Update the state of files which were removed in the amend
3241 # to "removed" in the dirstate.
3239 # to "removed" in the dirstate.
3242 removedfiles = set(wctx.removed()) & filestoamend
3240 removedfiles = set(wctx.removed()) & filestoamend
3243 for f in removedfiles:
3241 for f in removedfiles:
3244 dirstate.drop(f)
3242 dirstate.drop(f)
3245
3243
3246 return newid
3244 return newid
3247
3245
3248 def commiteditor(repo, ctx, subs, editform=''):
3246 def commiteditor(repo, ctx, subs, editform=''):
3249 if ctx.description():
3247 if ctx.description():
3250 return ctx.description()
3248 return ctx.description()
3251 return commitforceeditor(repo, ctx, subs, editform=editform,
3249 return commitforceeditor(repo, ctx, subs, editform=editform,
3252 unchangedmessagedetection=True)
3250 unchangedmessagedetection=True)
3253
3251
3254 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
3252 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
3255 editform='', unchangedmessagedetection=False):
3253 editform='', unchangedmessagedetection=False):
3256 if not extramsg:
3254 if not extramsg:
3257 extramsg = _("Leave message empty to abort commit.")
3255 extramsg = _("Leave message empty to abort commit.")
3258
3256
3259 forms = [e for e in editform.split('.') if e]
3257 forms = [e for e in editform.split('.') if e]
3260 forms.insert(0, 'changeset')
3258 forms.insert(0, 'changeset')
3261 templatetext = None
3259 templatetext = None
3262 while forms:
3260 while forms:
3263 ref = '.'.join(forms)
3261 ref = '.'.join(forms)
3264 if repo.ui.config('committemplate', ref):
3262 if repo.ui.config('committemplate', ref):
3265 templatetext = committext = buildcommittemplate(
3263 templatetext = committext = buildcommittemplate(
3266 repo, ctx, subs, extramsg, ref)
3264 repo, ctx, subs, extramsg, ref)
3267 break
3265 break
3268 forms.pop()
3266 forms.pop()
3269 else:
3267 else:
3270 committext = buildcommittext(repo, ctx, subs, extramsg)
3268 committext = buildcommittext(repo, ctx, subs, extramsg)
3271
3269
3272 # run editor in the repository root
3270 # run editor in the repository root
3273 olddir = pycompat.getcwd()
3271 olddir = pycompat.getcwd()
3274 os.chdir(repo.root)
3272 os.chdir(repo.root)
3275
3273
3276 # make in-memory changes visible to external process
3274 # make in-memory changes visible to external process
3277 tr = repo.currenttransaction()
3275 tr = repo.currenttransaction()
3278 repo.dirstate.write(tr)
3276 repo.dirstate.write(tr)
3279 pending = tr and tr.writepending() and repo.root
3277 pending = tr and tr.writepending() and repo.root
3280
3278
3281 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
3279 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
3282 editform=editform, pending=pending,
3280 editform=editform, pending=pending,
3283 repopath=repo.path, action='commit')
3281 repopath=repo.path, action='commit')
3284 text = editortext
3282 text = editortext
3285
3283
3286 # strip away anything below this special string (used for editors that want
3284 # strip away anything below this special string (used for editors that want
3287 # to display the diff)
3285 # to display the diff)
3288 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3286 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3289 if stripbelow:
3287 if stripbelow:
3290 text = text[:stripbelow.start()]
3288 text = text[:stripbelow.start()]
3291
3289
3292 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
3290 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
3293 os.chdir(olddir)
3291 os.chdir(olddir)
3294
3292
3295 if finishdesc:
3293 if finishdesc:
3296 text = finishdesc(text)
3294 text = finishdesc(text)
3297 if not text.strip():
3295 if not text.strip():
3298 raise error.Abort(_("empty commit message"))
3296 raise error.Abort(_("empty commit message"))
3299 if unchangedmessagedetection and editortext == templatetext:
3297 if unchangedmessagedetection and editortext == templatetext:
3300 raise error.Abort(_("commit message unchanged"))
3298 raise error.Abort(_("commit message unchanged"))
3301
3299
3302 return text
3300 return text
3303
3301
3304 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3302 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3305 ui = repo.ui
3303 ui = repo.ui
3306 spec = formatter.templatespec(ref, None, None)
3304 spec = formatter.templatespec(ref, None, None)
3307 t = changeset_templater(ui, repo, spec, None, {}, False)
3305 t = changeset_templater(ui, repo, spec, None, {}, False)
3308 t.t.cache.update((k, templater.unquotestring(v))
3306 t.t.cache.update((k, templater.unquotestring(v))
3309 for k, v in repo.ui.configitems('committemplate'))
3307 for k, v in repo.ui.configitems('committemplate'))
3310
3308
3311 if not extramsg:
3309 if not extramsg:
3312 extramsg = '' # ensure that extramsg is string
3310 extramsg = '' # ensure that extramsg is string
3313
3311
3314 ui.pushbuffer()
3312 ui.pushbuffer()
3315 t.show(ctx, extramsg=extramsg)
3313 t.show(ctx, extramsg=extramsg)
3316 return ui.popbuffer()
3314 return ui.popbuffer()
3317
3315
3318 def hgprefix(msg):
3316 def hgprefix(msg):
3319 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
3317 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
3320
3318
3321 def buildcommittext(repo, ctx, subs, extramsg):
3319 def buildcommittext(repo, ctx, subs, extramsg):
3322 edittext = []
3320 edittext = []
3323 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3321 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3324 if ctx.description():
3322 if ctx.description():
3325 edittext.append(ctx.description())
3323 edittext.append(ctx.description())
3326 edittext.append("")
3324 edittext.append("")
3327 edittext.append("") # Empty line between message and comments.
3325 edittext.append("") # Empty line between message and comments.
3328 edittext.append(hgprefix(_("Enter commit message."
3326 edittext.append(hgprefix(_("Enter commit message."
3329 " Lines beginning with 'HG:' are removed.")))
3327 " Lines beginning with 'HG:' are removed.")))
3330 edittext.append(hgprefix(extramsg))
3328 edittext.append(hgprefix(extramsg))
3331 edittext.append("HG: --")
3329 edittext.append("HG: --")
3332 edittext.append(hgprefix(_("user: %s") % ctx.user()))
3330 edittext.append(hgprefix(_("user: %s") % ctx.user()))
3333 if ctx.p2():
3331 if ctx.p2():
3334 edittext.append(hgprefix(_("branch merge")))
3332 edittext.append(hgprefix(_("branch merge")))
3335 if ctx.branch():
3333 if ctx.branch():
3336 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
3334 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
3337 if bookmarks.isactivewdirparent(repo):
3335 if bookmarks.isactivewdirparent(repo):
3338 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
3336 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
3339 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
3337 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
3340 edittext.extend([hgprefix(_("added %s") % f) for f in added])
3338 edittext.extend([hgprefix(_("added %s") % f) for f in added])
3341 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
3339 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
3342 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
3340 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
3343 if not added and not modified and not removed:
3341 if not added and not modified and not removed:
3344 edittext.append(hgprefix(_("no files changed")))
3342 edittext.append(hgprefix(_("no files changed")))
3345 edittext.append("")
3343 edittext.append("")
3346
3344
3347 return "\n".join(edittext)
3345 return "\n".join(edittext)
3348
3346
3349 def commitstatus(repo, node, branch, bheads=None, opts=None):
3347 def commitstatus(repo, node, branch, bheads=None, opts=None):
3350 if opts is None:
3348 if opts is None:
3351 opts = {}
3349 opts = {}
3352 ctx = repo[node]
3350 ctx = repo[node]
3353 parents = ctx.parents()
3351 parents = ctx.parents()
3354
3352
3355 if (not opts.get('amend') and bheads and node not in bheads and not
3353 if (not opts.get('amend') and bheads and node not in bheads and not
3356 [x for x in parents if x.node() in bheads and x.branch() == branch]):
3354 [x for x in parents if x.node() in bheads and x.branch() == branch]):
3357 repo.ui.status(_('created new head\n'))
3355 repo.ui.status(_('created new head\n'))
3358 # The message is not printed for initial roots. For the other
3356 # The message is not printed for initial roots. For the other
3359 # changesets, it is printed in the following situations:
3357 # changesets, it is printed in the following situations:
3360 #
3358 #
3361 # Par column: for the 2 parents with ...
3359 # Par column: for the 2 parents with ...
3362 # N: null or no parent
3360 # N: null or no parent
3363 # B: parent is on another named branch
3361 # B: parent is on another named branch
3364 # C: parent is a regular non head changeset
3362 # C: parent is a regular non head changeset
3365 # H: parent was a branch head of the current branch
3363 # H: parent was a branch head of the current branch
3366 # Msg column: whether we print "created new head" message
3364 # Msg column: whether we print "created new head" message
3367 # In the following, it is assumed that there already exists some
3365 # In the following, it is assumed that there already exists some
3368 # initial branch heads of the current branch, otherwise nothing is
3366 # initial branch heads of the current branch, otherwise nothing is
3369 # printed anyway.
3367 # printed anyway.
3370 #
3368 #
3371 # Par Msg Comment
3369 # Par Msg Comment
3372 # N N y additional topo root
3370 # N N y additional topo root
3373 #
3371 #
3374 # B N y additional branch root
3372 # B N y additional branch root
3375 # C N y additional topo head
3373 # C N y additional topo head
3376 # H N n usual case
3374 # H N n usual case
3377 #
3375 #
3378 # B B y weird additional branch root
3376 # B B y weird additional branch root
3379 # C B y branch merge
3377 # C B y branch merge
3380 # H B n merge with named branch
3378 # H B n merge with named branch
3381 #
3379 #
3382 # C C y additional head from merge
3380 # C C y additional head from merge
3383 # C H n merge with a head
3381 # C H n merge with a head
3384 #
3382 #
3385 # H H n head merge: head count decreases
3383 # H H n head merge: head count decreases
3386
3384
3387 if not opts.get('close_branch'):
3385 if not opts.get('close_branch'):
3388 for r in parents:
3386 for r in parents:
3389 if r.closesbranch() and r.branch() == branch:
3387 if r.closesbranch() and r.branch() == branch:
3390 repo.ui.status(_('reopening closed branch head %d\n') % r)
3388 repo.ui.status(_('reopening closed branch head %d\n') % r)
3391
3389
3392 if repo.ui.debugflag:
3390 if repo.ui.debugflag:
3393 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
3391 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
3394 elif repo.ui.verbose:
3392 elif repo.ui.verbose:
3395 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
3393 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
3396
3394
3397 def postcommitstatus(repo, pats, opts):
3395 def postcommitstatus(repo, pats, opts):
3398 return repo.status(match=scmutil.match(repo[None], pats, opts))
3396 return repo.status(match=scmutil.match(repo[None], pats, opts))
3399
3397
3400 def revert(ui, repo, ctx, parents, *pats, **opts):
3398 def revert(ui, repo, ctx, parents, *pats, **opts):
3401 opts = pycompat.byteskwargs(opts)
3399 opts = pycompat.byteskwargs(opts)
3402 parent, p2 = parents
3400 parent, p2 = parents
3403 node = ctx.node()
3401 node = ctx.node()
3404
3402
3405 mf = ctx.manifest()
3403 mf = ctx.manifest()
3406 if node == p2:
3404 if node == p2:
3407 parent = p2
3405 parent = p2
3408
3406
3409 # need all matching names in dirstate and manifest of target rev,
3407 # need all matching names in dirstate and manifest of target rev,
3410 # so have to walk both. do not print errors if files exist in one
3408 # so have to walk both. do not print errors if files exist in one
3411 # but not other. in both cases, filesets should be evaluated against
3409 # but not other. in both cases, filesets should be evaluated against
3412 # workingctx to get consistent result (issue4497). this means 'set:**'
3410 # workingctx to get consistent result (issue4497). this means 'set:**'
3413 # cannot be used to select missing files from target rev.
3411 # cannot be used to select missing files from target rev.
3414
3412
3415 # `names` is a mapping for all elements in working copy and target revision
3413 # `names` is a mapping for all elements in working copy and target revision
3416 # The mapping is in the form:
3414 # The mapping is in the form:
3417 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3415 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3418 names = {}
3416 names = {}
3419
3417
3420 with repo.wlock():
3418 with repo.wlock():
3421 ## filling of the `names` mapping
3419 ## filling of the `names` mapping
3422 # walk dirstate to fill `names`
3420 # walk dirstate to fill `names`
3423
3421
3424 interactive = opts.get('interactive', False)
3422 interactive = opts.get('interactive', False)
3425 wctx = repo[None]
3423 wctx = repo[None]
3426 m = scmutil.match(wctx, pats, opts)
3424 m = scmutil.match(wctx, pats, opts)
3427
3425
3428 # we'll need this later
3426 # we'll need this later
3429 targetsubs = sorted(s for s in wctx.substate if m(s))
3427 targetsubs = sorted(s for s in wctx.substate if m(s))
3430
3428
3431 if not m.always():
3429 if not m.always():
3432 matcher = matchmod.badmatch(m, lambda x, y: False)
3430 matcher = matchmod.badmatch(m, lambda x, y: False)
3433 for abs in wctx.walk(matcher):
3431 for abs in wctx.walk(matcher):
3434 names[abs] = m.rel(abs), m.exact(abs)
3432 names[abs] = m.rel(abs), m.exact(abs)
3435
3433
3436 # walk target manifest to fill `names`
3434 # walk target manifest to fill `names`
3437
3435
3438 def badfn(path, msg):
3436 def badfn(path, msg):
3439 if path in names:
3437 if path in names:
3440 return
3438 return
3441 if path in ctx.substate:
3439 if path in ctx.substate:
3442 return
3440 return
3443 path_ = path + '/'
3441 path_ = path + '/'
3444 for f in names:
3442 for f in names:
3445 if f.startswith(path_):
3443 if f.startswith(path_):
3446 return
3444 return
3447 ui.warn("%s: %s\n" % (m.rel(path), msg))
3445 ui.warn("%s: %s\n" % (m.rel(path), msg))
3448
3446
3449 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3447 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3450 if abs not in names:
3448 if abs not in names:
3451 names[abs] = m.rel(abs), m.exact(abs)
3449 names[abs] = m.rel(abs), m.exact(abs)
3452
3450
3453 # Find status of all file in `names`.
3451 # Find status of all file in `names`.
3454 m = scmutil.matchfiles(repo, names)
3452 m = scmutil.matchfiles(repo, names)
3455
3453
3456 changes = repo.status(node1=node, match=m,
3454 changes = repo.status(node1=node, match=m,
3457 unknown=True, ignored=True, clean=True)
3455 unknown=True, ignored=True, clean=True)
3458 else:
3456 else:
3459 changes = repo.status(node1=node, match=m)
3457 changes = repo.status(node1=node, match=m)
3460 for kind in changes:
3458 for kind in changes:
3461 for abs in kind:
3459 for abs in kind:
3462 names[abs] = m.rel(abs), m.exact(abs)
3460 names[abs] = m.rel(abs), m.exact(abs)
3463
3461
3464 m = scmutil.matchfiles(repo, names)
3462 m = scmutil.matchfiles(repo, names)
3465
3463
3466 modified = set(changes.modified)
3464 modified = set(changes.modified)
3467 added = set(changes.added)
3465 added = set(changes.added)
3468 removed = set(changes.removed)
3466 removed = set(changes.removed)
3469 _deleted = set(changes.deleted)
3467 _deleted = set(changes.deleted)
3470 unknown = set(changes.unknown)
3468 unknown = set(changes.unknown)
3471 unknown.update(changes.ignored)
3469 unknown.update(changes.ignored)
3472 clean = set(changes.clean)
3470 clean = set(changes.clean)
3473 modadded = set()
3471 modadded = set()
3474
3472
3475 # We need to account for the state of the file in the dirstate,
3473 # We need to account for the state of the file in the dirstate,
3476 # even when we revert against something else than parent. This will
3474 # even when we revert against something else than parent. This will
3477 # slightly alter the behavior of revert (doing back up or not, delete
3475 # slightly alter the behavior of revert (doing back up or not, delete
3478 # or just forget etc).
3476 # or just forget etc).
3479 if parent == node:
3477 if parent == node:
3480 dsmodified = modified
3478 dsmodified = modified
3481 dsadded = added
3479 dsadded = added
3482 dsremoved = removed
3480 dsremoved = removed
3483 # store all local modifications, useful later for rename detection
3481 # store all local modifications, useful later for rename detection
3484 localchanges = dsmodified | dsadded
3482 localchanges = dsmodified | dsadded
3485 modified, added, removed = set(), set(), set()
3483 modified, added, removed = set(), set(), set()
3486 else:
3484 else:
3487 changes = repo.status(node1=parent, match=m)
3485 changes = repo.status(node1=parent, match=m)
3488 dsmodified = set(changes.modified)
3486 dsmodified = set(changes.modified)
3489 dsadded = set(changes.added)
3487 dsadded = set(changes.added)
3490 dsremoved = set(changes.removed)
3488 dsremoved = set(changes.removed)
3491 # store all local modifications, useful later for rename detection
3489 # store all local modifications, useful later for rename detection
3492 localchanges = dsmodified | dsadded
3490 localchanges = dsmodified | dsadded
3493
3491
3494 # only take into account for removes between wc and target
3492 # only take into account for removes between wc and target
3495 clean |= dsremoved - removed
3493 clean |= dsremoved - removed
3496 dsremoved &= removed
3494 dsremoved &= removed
3497 # distinct between dirstate remove and other
3495 # distinct between dirstate remove and other
3498 removed -= dsremoved
3496 removed -= dsremoved
3499
3497
3500 modadded = added & dsmodified
3498 modadded = added & dsmodified
3501 added -= modadded
3499 added -= modadded
3502
3500
3503 # tell newly modified apart.
3501 # tell newly modified apart.
3504 dsmodified &= modified
3502 dsmodified &= modified
3505 dsmodified |= modified & dsadded # dirstate added may need backup
3503 dsmodified |= modified & dsadded # dirstate added may need backup
3506 modified -= dsmodified
3504 modified -= dsmodified
3507
3505
3508 # We need to wait for some post-processing to update this set
3506 # We need to wait for some post-processing to update this set
3509 # before making the distinction. The dirstate will be used for
3507 # before making the distinction. The dirstate will be used for
3510 # that purpose.
3508 # that purpose.
3511 dsadded = added
3509 dsadded = added
3512
3510
3513 # in case of merge, files that are actually added can be reported as
3511 # in case of merge, files that are actually added can be reported as
3514 # modified, we need to post process the result
3512 # modified, we need to post process the result
3515 if p2 != nullid:
3513 if p2 != nullid:
3516 mergeadd = set(dsmodified)
3514 mergeadd = set(dsmodified)
3517 for path in dsmodified:
3515 for path in dsmodified:
3518 if path in mf:
3516 if path in mf:
3519 mergeadd.remove(path)
3517 mergeadd.remove(path)
3520 dsadded |= mergeadd
3518 dsadded |= mergeadd
3521 dsmodified -= mergeadd
3519 dsmodified -= mergeadd
3522
3520
3523 # if f is a rename, update `names` to also revert the source
3521 # if f is a rename, update `names` to also revert the source
3524 cwd = repo.getcwd()
3522 cwd = repo.getcwd()
3525 for f in localchanges:
3523 for f in localchanges:
3526 src = repo.dirstate.copied(f)
3524 src = repo.dirstate.copied(f)
3527 # XXX should we check for rename down to target node?
3525 # XXX should we check for rename down to target node?
3528 if src and src not in names and repo.dirstate[src] == 'r':
3526 if src and src not in names and repo.dirstate[src] == 'r':
3529 dsremoved.add(src)
3527 dsremoved.add(src)
3530 names[src] = (repo.pathto(src, cwd), True)
3528 names[src] = (repo.pathto(src, cwd), True)
3531
3529
3532 # determine the exact nature of the deleted changesets
3530 # determine the exact nature of the deleted changesets
3533 deladded = set(_deleted)
3531 deladded = set(_deleted)
3534 for path in _deleted:
3532 for path in _deleted:
3535 if path in mf:
3533 if path in mf:
3536 deladded.remove(path)
3534 deladded.remove(path)
3537 deleted = _deleted - deladded
3535 deleted = _deleted - deladded
3538
3536
3539 # distinguish between file to forget and the other
3537 # distinguish between file to forget and the other
3540 added = set()
3538 added = set()
3541 for abs in dsadded:
3539 for abs in dsadded:
3542 if repo.dirstate[abs] != 'a':
3540 if repo.dirstate[abs] != 'a':
3543 added.add(abs)
3541 added.add(abs)
3544 dsadded -= added
3542 dsadded -= added
3545
3543
3546 for abs in deladded:
3544 for abs in deladded:
3547 if repo.dirstate[abs] == 'a':
3545 if repo.dirstate[abs] == 'a':
3548 dsadded.add(abs)
3546 dsadded.add(abs)
3549 deladded -= dsadded
3547 deladded -= dsadded
3550
3548
3551 # For files marked as removed, we check if an unknown file is present at
3549 # For files marked as removed, we check if an unknown file is present at
3552 # the same path. If a such file exists it may need to be backed up.
3550 # the same path. If a such file exists it may need to be backed up.
3553 # Making the distinction at this stage helps have simpler backup
3551 # Making the distinction at this stage helps have simpler backup
3554 # logic.
3552 # logic.
3555 removunk = set()
3553 removunk = set()
3556 for abs in removed:
3554 for abs in removed:
3557 target = repo.wjoin(abs)
3555 target = repo.wjoin(abs)
3558 if os.path.lexists(target):
3556 if os.path.lexists(target):
3559 removunk.add(abs)
3557 removunk.add(abs)
3560 removed -= removunk
3558 removed -= removunk
3561
3559
3562 dsremovunk = set()
3560 dsremovunk = set()
3563 for abs in dsremoved:
3561 for abs in dsremoved:
3564 target = repo.wjoin(abs)
3562 target = repo.wjoin(abs)
3565 if os.path.lexists(target):
3563 if os.path.lexists(target):
3566 dsremovunk.add(abs)
3564 dsremovunk.add(abs)
3567 dsremoved -= dsremovunk
3565 dsremoved -= dsremovunk
3568
3566
3569 # action to be actually performed by revert
3567 # action to be actually performed by revert
3570 # (<list of file>, message>) tuple
3568 # (<list of file>, message>) tuple
3571 actions = {'revert': ([], _('reverting %s\n')),
3569 actions = {'revert': ([], _('reverting %s\n')),
3572 'add': ([], _('adding %s\n')),
3570 'add': ([], _('adding %s\n')),
3573 'remove': ([], _('removing %s\n')),
3571 'remove': ([], _('removing %s\n')),
3574 'drop': ([], _('removing %s\n')),
3572 'drop': ([], _('removing %s\n')),
3575 'forget': ([], _('forgetting %s\n')),
3573 'forget': ([], _('forgetting %s\n')),
3576 'undelete': ([], _('undeleting %s\n')),
3574 'undelete': ([], _('undeleting %s\n')),
3577 'noop': (None, _('no changes needed to %s\n')),
3575 'noop': (None, _('no changes needed to %s\n')),
3578 'unknown': (None, _('file not managed: %s\n')),
3576 'unknown': (None, _('file not managed: %s\n')),
3579 }
3577 }
3580
3578
3581 # "constant" that convey the backup strategy.
3579 # "constant" that convey the backup strategy.
3582 # All set to `discard` if `no-backup` is set do avoid checking
3580 # All set to `discard` if `no-backup` is set do avoid checking
3583 # no_backup lower in the code.
3581 # no_backup lower in the code.
3584 # These values are ordered for comparison purposes
3582 # These values are ordered for comparison purposes
3585 backupinteractive = 3 # do backup if interactively modified
3583 backupinteractive = 3 # do backup if interactively modified
3586 backup = 2 # unconditionally do backup
3584 backup = 2 # unconditionally do backup
3587 check = 1 # check if the existing file differs from target
3585 check = 1 # check if the existing file differs from target
3588 discard = 0 # never do backup
3586 discard = 0 # never do backup
3589 if opts.get('no_backup'):
3587 if opts.get('no_backup'):
3590 backupinteractive = backup = check = discard
3588 backupinteractive = backup = check = discard
3591 if interactive:
3589 if interactive:
3592 dsmodifiedbackup = backupinteractive
3590 dsmodifiedbackup = backupinteractive
3593 else:
3591 else:
3594 dsmodifiedbackup = backup
3592 dsmodifiedbackup = backup
3595 tobackup = set()
3593 tobackup = set()
3596
3594
3597 backupanddel = actions['remove']
3595 backupanddel = actions['remove']
3598 if not opts.get('no_backup'):
3596 if not opts.get('no_backup'):
3599 backupanddel = actions['drop']
3597 backupanddel = actions['drop']
3600
3598
3601 disptable = (
3599 disptable = (
3602 # dispatch table:
3600 # dispatch table:
3603 # file state
3601 # file state
3604 # action
3602 # action
3605 # make backup
3603 # make backup
3606
3604
3607 ## Sets that results that will change file on disk
3605 ## Sets that results that will change file on disk
3608 # Modified compared to target, no local change
3606 # Modified compared to target, no local change
3609 (modified, actions['revert'], discard),
3607 (modified, actions['revert'], discard),
3610 # Modified compared to target, but local file is deleted
3608 # Modified compared to target, but local file is deleted
3611 (deleted, actions['revert'], discard),
3609 (deleted, actions['revert'], discard),
3612 # Modified compared to target, local change
3610 # Modified compared to target, local change
3613 (dsmodified, actions['revert'], dsmodifiedbackup),
3611 (dsmodified, actions['revert'], dsmodifiedbackup),
3614 # Added since target
3612 # Added since target
3615 (added, actions['remove'], discard),
3613 (added, actions['remove'], discard),
3616 # Added in working directory
3614 # Added in working directory
3617 (dsadded, actions['forget'], discard),
3615 (dsadded, actions['forget'], discard),
3618 # Added since target, have local modification
3616 # Added since target, have local modification
3619 (modadded, backupanddel, backup),
3617 (modadded, backupanddel, backup),
3620 # Added since target but file is missing in working directory
3618 # Added since target but file is missing in working directory
3621 (deladded, actions['drop'], discard),
3619 (deladded, actions['drop'], discard),
3622 # Removed since target, before working copy parent
3620 # Removed since target, before working copy parent
3623 (removed, actions['add'], discard),
3621 (removed, actions['add'], discard),
3624 # Same as `removed` but an unknown file exists at the same path
3622 # Same as `removed` but an unknown file exists at the same path
3625 (removunk, actions['add'], check),
3623 (removunk, actions['add'], check),
3626 # Removed since targe, marked as such in working copy parent
3624 # Removed since targe, marked as such in working copy parent
3627 (dsremoved, actions['undelete'], discard),
3625 (dsremoved, actions['undelete'], discard),
3628 # Same as `dsremoved` but an unknown file exists at the same path
3626 # Same as `dsremoved` but an unknown file exists at the same path
3629 (dsremovunk, actions['undelete'], check),
3627 (dsremovunk, actions['undelete'], check),
3630 ## the following sets does not result in any file changes
3628 ## the following sets does not result in any file changes
3631 # File with no modification
3629 # File with no modification
3632 (clean, actions['noop'], discard),
3630 (clean, actions['noop'], discard),
3633 # Existing file, not tracked anywhere
3631 # Existing file, not tracked anywhere
3634 (unknown, actions['unknown'], discard),
3632 (unknown, actions['unknown'], discard),
3635 )
3633 )
3636
3634
3637 for abs, (rel, exact) in sorted(names.items()):
3635 for abs, (rel, exact) in sorted(names.items()):
3638 # target file to be touch on disk (relative to cwd)
3636 # target file to be touch on disk (relative to cwd)
3639 target = repo.wjoin(abs)
3637 target = repo.wjoin(abs)
3640 # search the entry in the dispatch table.
3638 # search the entry in the dispatch table.
3641 # if the file is in any of these sets, it was touched in the working
3639 # if the file is in any of these sets, it was touched in the working
3642 # directory parent and we are sure it needs to be reverted.
3640 # directory parent and we are sure it needs to be reverted.
3643 for table, (xlist, msg), dobackup in disptable:
3641 for table, (xlist, msg), dobackup in disptable:
3644 if abs not in table:
3642 if abs not in table:
3645 continue
3643 continue
3646 if xlist is not None:
3644 if xlist is not None:
3647 xlist.append(abs)
3645 xlist.append(abs)
3648 if dobackup:
3646 if dobackup:
3649 # If in interactive mode, don't automatically create
3647 # If in interactive mode, don't automatically create
3650 # .orig files (issue4793)
3648 # .orig files (issue4793)
3651 if dobackup == backupinteractive:
3649 if dobackup == backupinteractive:
3652 tobackup.add(abs)
3650 tobackup.add(abs)
3653 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3651 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3654 bakname = scmutil.origpath(ui, repo, rel)
3652 bakname = scmutil.origpath(ui, repo, rel)
3655 ui.note(_('saving current version of %s as %s\n') %
3653 ui.note(_('saving current version of %s as %s\n') %
3656 (rel, bakname))
3654 (rel, bakname))
3657 if not opts.get('dry_run'):
3655 if not opts.get('dry_run'):
3658 if interactive:
3656 if interactive:
3659 util.copyfile(target, bakname)
3657 util.copyfile(target, bakname)
3660 else:
3658 else:
3661 util.rename(target, bakname)
3659 util.rename(target, bakname)
3662 if ui.verbose or not exact:
3660 if ui.verbose or not exact:
3663 if not isinstance(msg, bytes):
3661 if not isinstance(msg, bytes):
3664 msg = msg(abs)
3662 msg = msg(abs)
3665 ui.status(msg % rel)
3663 ui.status(msg % rel)
3666 elif exact:
3664 elif exact:
3667 ui.warn(msg % rel)
3665 ui.warn(msg % rel)
3668 break
3666 break
3669
3667
3670 if not opts.get('dry_run'):
3668 if not opts.get('dry_run'):
3671 needdata = ('revert', 'add', 'undelete')
3669 needdata = ('revert', 'add', 'undelete')
3672 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3670 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3673 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3671 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3674
3672
3675 if targetsubs:
3673 if targetsubs:
3676 # Revert the subrepos on the revert list
3674 # Revert the subrepos on the revert list
3677 for sub in targetsubs:
3675 for sub in targetsubs:
3678 try:
3676 try:
3679 wctx.sub(sub).revert(ctx.substate[sub], *pats,
3677 wctx.sub(sub).revert(ctx.substate[sub], *pats,
3680 **pycompat.strkwargs(opts))
3678 **pycompat.strkwargs(opts))
3681 except KeyError:
3679 except KeyError:
3682 raise error.Abort("subrepository '%s' does not exist in %s!"
3680 raise error.Abort("subrepository '%s' does not exist in %s!"
3683 % (sub, short(ctx.node())))
3681 % (sub, short(ctx.node())))
3684
3682
3685 def _revertprefetch(repo, ctx, *files):
3683 def _revertprefetch(repo, ctx, *files):
3686 """Let extension changing the storage layer prefetch content"""
3684 """Let extension changing the storage layer prefetch content"""
3687
3685
3688 def _performrevert(repo, parents, ctx, actions, interactive=False,
3686 def _performrevert(repo, parents, ctx, actions, interactive=False,
3689 tobackup=None):
3687 tobackup=None):
3690 """function that actually perform all the actions computed for revert
3688 """function that actually perform all the actions computed for revert
3691
3689
3692 This is an independent function to let extension to plug in and react to
3690 This is an independent function to let extension to plug in and react to
3693 the imminent revert.
3691 the imminent revert.
3694
3692
3695 Make sure you have the working directory locked when calling this function.
3693 Make sure you have the working directory locked when calling this function.
3696 """
3694 """
3697 parent, p2 = parents
3695 parent, p2 = parents
3698 node = ctx.node()
3696 node = ctx.node()
3699 excluded_files = []
3697 excluded_files = []
3700 matcher_opts = {"exclude": excluded_files}
3698 matcher_opts = {"exclude": excluded_files}
3701
3699
3702 def checkout(f):
3700 def checkout(f):
3703 fc = ctx[f]
3701 fc = ctx[f]
3704 repo.wwrite(f, fc.data(), fc.flags())
3702 repo.wwrite(f, fc.data(), fc.flags())
3705
3703
3706 def doremove(f):
3704 def doremove(f):
3707 try:
3705 try:
3708 repo.wvfs.unlinkpath(f)
3706 repo.wvfs.unlinkpath(f)
3709 except OSError:
3707 except OSError:
3710 pass
3708 pass
3711 repo.dirstate.remove(f)
3709 repo.dirstate.remove(f)
3712
3710
3713 audit_path = pathutil.pathauditor(repo.root, cached=True)
3711 audit_path = pathutil.pathauditor(repo.root, cached=True)
3714 for f in actions['forget'][0]:
3712 for f in actions['forget'][0]:
3715 if interactive:
3713 if interactive:
3716 choice = repo.ui.promptchoice(
3714 choice = repo.ui.promptchoice(
3717 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3715 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3718 if choice == 0:
3716 if choice == 0:
3719 repo.dirstate.drop(f)
3717 repo.dirstate.drop(f)
3720 else:
3718 else:
3721 excluded_files.append(repo.wjoin(f))
3719 excluded_files.append(repo.wjoin(f))
3722 else:
3720 else:
3723 repo.dirstate.drop(f)
3721 repo.dirstate.drop(f)
3724 for f in actions['remove'][0]:
3722 for f in actions['remove'][0]:
3725 audit_path(f)
3723 audit_path(f)
3726 if interactive:
3724 if interactive:
3727 choice = repo.ui.promptchoice(
3725 choice = repo.ui.promptchoice(
3728 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3726 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3729 if choice == 0:
3727 if choice == 0:
3730 doremove(f)
3728 doremove(f)
3731 else:
3729 else:
3732 excluded_files.append(repo.wjoin(f))
3730 excluded_files.append(repo.wjoin(f))
3733 else:
3731 else:
3734 doremove(f)
3732 doremove(f)
3735 for f in actions['drop'][0]:
3733 for f in actions['drop'][0]:
3736 audit_path(f)
3734 audit_path(f)
3737 repo.dirstate.remove(f)
3735 repo.dirstate.remove(f)
3738
3736
3739 normal = None
3737 normal = None
3740 if node == parent:
3738 if node == parent:
3741 # We're reverting to our parent. If possible, we'd like status
3739 # We're reverting to our parent. If possible, we'd like status
3742 # to report the file as clean. We have to use normallookup for
3740 # to report the file as clean. We have to use normallookup for
3743 # merges to avoid losing information about merged/dirty files.
3741 # merges to avoid losing information about merged/dirty files.
3744 if p2 != nullid:
3742 if p2 != nullid:
3745 normal = repo.dirstate.normallookup
3743 normal = repo.dirstate.normallookup
3746 else:
3744 else:
3747 normal = repo.dirstate.normal
3745 normal = repo.dirstate.normal
3748
3746
3749 newlyaddedandmodifiedfiles = set()
3747 newlyaddedandmodifiedfiles = set()
3750 if interactive:
3748 if interactive:
3751 # Prompt the user for changes to revert
3749 # Prompt the user for changes to revert
3752 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3750 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3753 m = scmutil.match(ctx, torevert, matcher_opts)
3751 m = scmutil.match(ctx, torevert, matcher_opts)
3754 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3752 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3755 diffopts.nodates = True
3753 diffopts.nodates = True
3756 diffopts.git = True
3754 diffopts.git = True
3757 operation = 'discard'
3755 operation = 'discard'
3758 reversehunks = True
3756 reversehunks = True
3759 if node != parent:
3757 if node != parent:
3760 operation = 'apply'
3758 operation = 'apply'
3761 reversehunks = False
3759 reversehunks = False
3762 if reversehunks:
3760 if reversehunks:
3763 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3761 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3764 else:
3762 else:
3765 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3763 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3766 originalchunks = patch.parsepatch(diff)
3764 originalchunks = patch.parsepatch(diff)
3767
3765
3768 try:
3766 try:
3769
3767
3770 chunks, opts = recordfilter(repo.ui, originalchunks,
3768 chunks, opts = recordfilter(repo.ui, originalchunks,
3771 operation=operation)
3769 operation=operation)
3772 if reversehunks:
3770 if reversehunks:
3773 chunks = patch.reversehunks(chunks)
3771 chunks = patch.reversehunks(chunks)
3774
3772
3775 except error.PatchError as err:
3773 except error.PatchError as err:
3776 raise error.Abort(_('error parsing patch: %s') % err)
3774 raise error.Abort(_('error parsing patch: %s') % err)
3777
3775
3778 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3776 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3779 if tobackup is None:
3777 if tobackup is None:
3780 tobackup = set()
3778 tobackup = set()
3781 # Apply changes
3779 # Apply changes
3782 fp = stringio()
3780 fp = stringio()
3783 for c in chunks:
3781 for c in chunks:
3784 # Create a backup file only if this hunk should be backed up
3782 # Create a backup file only if this hunk should be backed up
3785 if ishunk(c) and c.header.filename() in tobackup:
3783 if ishunk(c) and c.header.filename() in tobackup:
3786 abs = c.header.filename()
3784 abs = c.header.filename()
3787 target = repo.wjoin(abs)
3785 target = repo.wjoin(abs)
3788 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3786 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3789 util.copyfile(target, bakname)
3787 util.copyfile(target, bakname)
3790 tobackup.remove(abs)
3788 tobackup.remove(abs)
3791 c.write(fp)
3789 c.write(fp)
3792 dopatch = fp.tell()
3790 dopatch = fp.tell()
3793 fp.seek(0)
3791 fp.seek(0)
3794 if dopatch:
3792 if dopatch:
3795 try:
3793 try:
3796 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3794 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3797 except error.PatchError as err:
3795 except error.PatchError as err:
3798 raise error.Abort(str(err))
3796 raise error.Abort(str(err))
3799 del fp
3797 del fp
3800 else:
3798 else:
3801 for f in actions['revert'][0]:
3799 for f in actions['revert'][0]:
3802 checkout(f)
3800 checkout(f)
3803 if normal:
3801 if normal:
3804 normal(f)
3802 normal(f)
3805
3803
3806 for f in actions['add'][0]:
3804 for f in actions['add'][0]:
3807 # Don't checkout modified files, they are already created by the diff
3805 # Don't checkout modified files, they are already created by the diff
3808 if f not in newlyaddedandmodifiedfiles:
3806 if f not in newlyaddedandmodifiedfiles:
3809 checkout(f)
3807 checkout(f)
3810 repo.dirstate.add(f)
3808 repo.dirstate.add(f)
3811
3809
3812 normal = repo.dirstate.normallookup
3810 normal = repo.dirstate.normallookup
3813 if node == parent and p2 == nullid:
3811 if node == parent and p2 == nullid:
3814 normal = repo.dirstate.normal
3812 normal = repo.dirstate.normal
3815 for f in actions['undelete'][0]:
3813 for f in actions['undelete'][0]:
3816 checkout(f)
3814 checkout(f)
3817 normal(f)
3815 normal(f)
3818
3816
3819 copied = copies.pathcopies(repo[parent], ctx)
3817 copied = copies.pathcopies(repo[parent], ctx)
3820
3818
3821 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3819 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3822 if f in copied:
3820 if f in copied:
3823 repo.dirstate.copy(copied[f], f)
3821 repo.dirstate.copy(copied[f], f)
3824
3822
3825 class command(registrar.command):
3823 class command(registrar.command):
3826 """deprecated: used registrar.command instead"""
3824 """deprecated: used registrar.command instead"""
3827 def _doregister(self, func, name, *args, **kwargs):
3825 def _doregister(self, func, name, *args, **kwargs):
3828 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3826 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3829 return super(command, self)._doregister(func, name, *args, **kwargs)
3827 return super(command, self)._doregister(func, name, *args, **kwargs)
3830
3828
3831 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3829 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3832 # commands.outgoing. "missing" is "missing" of the result of
3830 # commands.outgoing. "missing" is "missing" of the result of
3833 # "findcommonoutgoing()"
3831 # "findcommonoutgoing()"
3834 outgoinghooks = util.hooks()
3832 outgoinghooks = util.hooks()
3835
3833
3836 # a list of (ui, repo) functions called by commands.summary
3834 # a list of (ui, repo) functions called by commands.summary
3837 summaryhooks = util.hooks()
3835 summaryhooks = util.hooks()
3838
3836
3839 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3837 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3840 #
3838 #
3841 # functions should return tuple of booleans below, if 'changes' is None:
3839 # functions should return tuple of booleans below, if 'changes' is None:
3842 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3840 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3843 #
3841 #
3844 # otherwise, 'changes' is a tuple of tuples below:
3842 # otherwise, 'changes' is a tuple of tuples below:
3845 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3843 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3846 # - (desturl, destbranch, destpeer, outgoing)
3844 # - (desturl, destbranch, destpeer, outgoing)
3847 summaryremotehooks = util.hooks()
3845 summaryremotehooks = util.hooks()
3848
3846
3849 # A list of state files kept by multistep operations like graft.
3847 # A list of state files kept by multistep operations like graft.
3850 # Since graft cannot be aborted, it is considered 'clearable' by update.
3848 # Since graft cannot be aborted, it is considered 'clearable' by update.
3851 # note: bisect is intentionally excluded
3849 # note: bisect is intentionally excluded
3852 # (state file, clearable, allowcommit, error, hint)
3850 # (state file, clearable, allowcommit, error, hint)
3853 unfinishedstates = [
3851 unfinishedstates = [
3854 ('graftstate', True, False, _('graft in progress'),
3852 ('graftstate', True, False, _('graft in progress'),
3855 _("use 'hg graft --continue' or 'hg update' to abort")),
3853 _("use 'hg graft --continue' or 'hg update' to abort")),
3856 ('updatestate', True, False, _('last update was interrupted'),
3854 ('updatestate', True, False, _('last update was interrupted'),
3857 _("use 'hg update' to get a consistent checkout"))
3855 _("use 'hg update' to get a consistent checkout"))
3858 ]
3856 ]
3859
3857
3860 def checkunfinished(repo, commit=False):
3858 def checkunfinished(repo, commit=False):
3861 '''Look for an unfinished multistep operation, like graft, and abort
3859 '''Look for an unfinished multistep operation, like graft, and abort
3862 if found. It's probably good to check this right before
3860 if found. It's probably good to check this right before
3863 bailifchanged().
3861 bailifchanged().
3864 '''
3862 '''
3865 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3863 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3866 if commit and allowcommit:
3864 if commit and allowcommit:
3867 continue
3865 continue
3868 if repo.vfs.exists(f):
3866 if repo.vfs.exists(f):
3869 raise error.Abort(msg, hint=hint)
3867 raise error.Abort(msg, hint=hint)
3870
3868
3871 def clearunfinished(repo):
3869 def clearunfinished(repo):
3872 '''Check for unfinished operations (as above), and clear the ones
3870 '''Check for unfinished operations (as above), and clear the ones
3873 that are clearable.
3871 that are clearable.
3874 '''
3872 '''
3875 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3873 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3876 if not clearable and repo.vfs.exists(f):
3874 if not clearable and repo.vfs.exists(f):
3877 raise error.Abort(msg, hint=hint)
3875 raise error.Abort(msg, hint=hint)
3878 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3876 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3879 if clearable and repo.vfs.exists(f):
3877 if clearable and repo.vfs.exists(f):
3880 util.unlink(repo.vfs.join(f))
3878 util.unlink(repo.vfs.join(f))
3881
3879
3882 afterresolvedstates = [
3880 afterresolvedstates = [
3883 ('graftstate',
3881 ('graftstate',
3884 _('hg graft --continue')),
3882 _('hg graft --continue')),
3885 ]
3883 ]
3886
3884
3887 def howtocontinue(repo):
3885 def howtocontinue(repo):
3888 '''Check for an unfinished operation and return the command to finish
3886 '''Check for an unfinished operation and return the command to finish
3889 it.
3887 it.
3890
3888
3891 afterresolvedstates tuples define a .hg/{file} and the corresponding
3889 afterresolvedstates tuples define a .hg/{file} and the corresponding
3892 command needed to finish it.
3890 command needed to finish it.
3893
3891
3894 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3892 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3895 a boolean.
3893 a boolean.
3896 '''
3894 '''
3897 contmsg = _("continue: %s")
3895 contmsg = _("continue: %s")
3898 for f, msg in afterresolvedstates:
3896 for f, msg in afterresolvedstates:
3899 if repo.vfs.exists(f):
3897 if repo.vfs.exists(f):
3900 return contmsg % msg, True
3898 return contmsg % msg, True
3901 if repo[None].dirty(missing=True, merge=False, branch=False):
3899 if repo[None].dirty(missing=True, merge=False, branch=False):
3902 return contmsg % _("hg commit"), False
3900 return contmsg % _("hg commit"), False
3903 return None, None
3901 return None, None
3904
3902
3905 def checkafterresolved(repo):
3903 def checkafterresolved(repo):
3906 '''Inform the user about the next action after completing hg resolve
3904 '''Inform the user about the next action after completing hg resolve
3907
3905
3908 If there's a matching afterresolvedstates, howtocontinue will yield
3906 If there's a matching afterresolvedstates, howtocontinue will yield
3909 repo.ui.warn as the reporter.
3907 repo.ui.warn as the reporter.
3910
3908
3911 Otherwise, it will yield repo.ui.note.
3909 Otherwise, it will yield repo.ui.note.
3912 '''
3910 '''
3913 msg, warning = howtocontinue(repo)
3911 msg, warning = howtocontinue(repo)
3914 if msg is not None:
3912 if msg is not None:
3915 if warning:
3913 if warning:
3916 repo.ui.warn("%s\n" % msg)
3914 repo.ui.warn("%s\n" % msg)
3917 else:
3915 else:
3918 repo.ui.note("%s\n" % msg)
3916 repo.ui.note("%s\n" % msg)
3919
3917
3920 def wrongtooltocontinue(repo, task):
3918 def wrongtooltocontinue(repo, task):
3921 '''Raise an abort suggesting how to properly continue if there is an
3919 '''Raise an abort suggesting how to properly continue if there is an
3922 active task.
3920 active task.
3923
3921
3924 Uses howtocontinue() to find the active task.
3922 Uses howtocontinue() to find the active task.
3925
3923
3926 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3924 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3927 a hint.
3925 a hint.
3928 '''
3926 '''
3929 after = howtocontinue(repo)
3927 after = howtocontinue(repo)
3930 hint = None
3928 hint = None
3931 if after[1]:
3929 if after[1]:
3932 hint = after[0]
3930 hint = after[0]
3933 raise error.Abort(_('no %s in progress') % task, hint=hint)
3931 raise error.Abort(_('no %s in progress') % task, hint=hint)
@@ -1,5623 +1,5617 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 )
22 )
23 from . import (
23 from . import (
24 archival,
24 archival,
25 bookmarks,
25 bookmarks,
26 bundle2,
26 bundle2,
27 changegroup,
27 changegroup,
28 cmdutil,
28 cmdutil,
29 copies,
29 copies,
30 debugcommands as debugcommandsmod,
30 debugcommands as debugcommandsmod,
31 destutil,
31 destutil,
32 dirstateguard,
32 dirstateguard,
33 discovery,
33 discovery,
34 encoding,
34 encoding,
35 error,
35 error,
36 exchange,
36 exchange,
37 extensions,
37 extensions,
38 formatter,
38 formatter,
39 graphmod,
39 graphmod,
40 hbisect,
40 hbisect,
41 help,
41 help,
42 hg,
42 hg,
43 lock as lockmod,
43 lock as lockmod,
44 merge as mergemod,
44 merge as mergemod,
45 obsolete,
45 obsolete,
46 patch,
46 patch,
47 phases,
47 phases,
48 pycompat,
48 pycompat,
49 rcutil,
49 rcutil,
50 registrar,
50 registrar,
51 revsetlang,
51 revsetlang,
52 rewriteutil,
52 rewriteutil,
53 scmutil,
53 scmutil,
54 server,
54 server,
55 sshserver,
55 sshserver,
56 streamclone,
56 streamclone,
57 tags as tagsmod,
57 tags as tagsmod,
58 templatekw,
58 templatekw,
59 ui as uimod,
59 ui as uimod,
60 util,
60 util,
61 )
61 )
62
62
63 release = lockmod.release
63 release = lockmod.release
64
64
65 table = {}
65 table = {}
66 table.update(debugcommandsmod.command._table)
66 table.update(debugcommandsmod.command._table)
67
67
68 command = registrar.command(table)
68 command = registrar.command(table)
69 readonly = registrar.command.readonly
69 readonly = registrar.command.readonly
70
70
71 # common command options
71 # common command options
72
72
73 globalopts = [
73 globalopts = [
74 ('R', 'repository', '',
74 ('R', 'repository', '',
75 _('repository root directory or name of overlay bundle file'),
75 _('repository root directory or name of overlay bundle file'),
76 _('REPO')),
76 _('REPO')),
77 ('', 'cwd', '',
77 ('', 'cwd', '',
78 _('change working directory'), _('DIR')),
78 _('change working directory'), _('DIR')),
79 ('y', 'noninteractive', None,
79 ('y', 'noninteractive', None,
80 _('do not prompt, automatically pick the first choice for all prompts')),
80 _('do not prompt, automatically pick the first choice for all prompts')),
81 ('q', 'quiet', None, _('suppress output')),
81 ('q', 'quiet', None, _('suppress output')),
82 ('v', 'verbose', None, _('enable additional output')),
82 ('v', 'verbose', None, _('enable additional output')),
83 ('', 'color', '',
83 ('', 'color', '',
84 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
84 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
85 # and should not be translated
85 # and should not be translated
86 _("when to colorize (boolean, always, auto, never, or debug)"),
86 _("when to colorize (boolean, always, auto, never, or debug)"),
87 _('TYPE')),
87 _('TYPE')),
88 ('', 'config', [],
88 ('', 'config', [],
89 _('set/override config option (use \'section.name=value\')'),
89 _('set/override config option (use \'section.name=value\')'),
90 _('CONFIG')),
90 _('CONFIG')),
91 ('', 'debug', None, _('enable debugging output')),
91 ('', 'debug', None, _('enable debugging output')),
92 ('', 'debugger', None, _('start debugger')),
92 ('', 'debugger', None, _('start debugger')),
93 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
93 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
94 _('ENCODE')),
94 _('ENCODE')),
95 ('', 'encodingmode', encoding.encodingmode,
95 ('', 'encodingmode', encoding.encodingmode,
96 _('set the charset encoding mode'), _('MODE')),
96 _('set the charset encoding mode'), _('MODE')),
97 ('', 'traceback', None, _('always print a traceback on exception')),
97 ('', 'traceback', None, _('always print a traceback on exception')),
98 ('', 'time', None, _('time how long the command takes')),
98 ('', 'time', None, _('time how long the command takes')),
99 ('', 'profile', None, _('print command execution profile')),
99 ('', 'profile', None, _('print command execution profile')),
100 ('', 'version', None, _('output version information and exit')),
100 ('', 'version', None, _('output version information and exit')),
101 ('h', 'help', None, _('display help and exit')),
101 ('h', 'help', None, _('display help and exit')),
102 ('', 'hidden', False, _('consider hidden changesets')),
102 ('', 'hidden', False, _('consider hidden changesets')),
103 ('', 'pager', 'auto',
103 ('', 'pager', 'auto',
104 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
104 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
105 ]
105 ]
106
106
107 dryrunopts = cmdutil.dryrunopts
107 dryrunopts = cmdutil.dryrunopts
108 remoteopts = cmdutil.remoteopts
108 remoteopts = cmdutil.remoteopts
109 walkopts = cmdutil.walkopts
109 walkopts = cmdutil.walkopts
110 commitopts = cmdutil.commitopts
110 commitopts = cmdutil.commitopts
111 commitopts2 = cmdutil.commitopts2
111 commitopts2 = cmdutil.commitopts2
112 formatteropts = cmdutil.formatteropts
112 formatteropts = cmdutil.formatteropts
113 templateopts = cmdutil.templateopts
113 templateopts = cmdutil.templateopts
114 logopts = cmdutil.logopts
114 logopts = cmdutil.logopts
115 diffopts = cmdutil.diffopts
115 diffopts = cmdutil.diffopts
116 diffwsopts = cmdutil.diffwsopts
116 diffwsopts = cmdutil.diffwsopts
117 diffopts2 = cmdutil.diffopts2
117 diffopts2 = cmdutil.diffopts2
118 mergetoolopts = cmdutil.mergetoolopts
118 mergetoolopts = cmdutil.mergetoolopts
119 similarityopts = cmdutil.similarityopts
119 similarityopts = cmdutil.similarityopts
120 subrepoopts = cmdutil.subrepoopts
120 subrepoopts = cmdutil.subrepoopts
121 debugrevlogopts = cmdutil.debugrevlogopts
121 debugrevlogopts = cmdutil.debugrevlogopts
122
122
123 # Commands start here, listed alphabetically
123 # Commands start here, listed alphabetically
124
124
125 @command('^add',
125 @command('^add',
126 walkopts + subrepoopts + dryrunopts,
126 walkopts + subrepoopts + dryrunopts,
127 _('[OPTION]... [FILE]...'),
127 _('[OPTION]... [FILE]...'),
128 inferrepo=True)
128 inferrepo=True)
129 def add(ui, repo, *pats, **opts):
129 def add(ui, repo, *pats, **opts):
130 """add the specified files on the next commit
130 """add the specified files on the next commit
131
131
132 Schedule files to be version controlled and added to the
132 Schedule files to be version controlled and added to the
133 repository.
133 repository.
134
134
135 The files will be added to the repository at the next commit. To
135 The files will be added to the repository at the next commit. To
136 undo an add before that, see :hg:`forget`.
136 undo an add before that, see :hg:`forget`.
137
137
138 If no names are given, add all files to the repository (except
138 If no names are given, add all files to the repository (except
139 files matching ``.hgignore``).
139 files matching ``.hgignore``).
140
140
141 .. container:: verbose
141 .. container:: verbose
142
142
143 Examples:
143 Examples:
144
144
145 - New (unknown) files are added
145 - New (unknown) files are added
146 automatically by :hg:`add`::
146 automatically by :hg:`add`::
147
147
148 $ ls
148 $ ls
149 foo.c
149 foo.c
150 $ hg status
150 $ hg status
151 ? foo.c
151 ? foo.c
152 $ hg add
152 $ hg add
153 adding foo.c
153 adding foo.c
154 $ hg status
154 $ hg status
155 A foo.c
155 A foo.c
156
156
157 - Specific files to be added can be specified::
157 - Specific files to be added can be specified::
158
158
159 $ ls
159 $ ls
160 bar.c foo.c
160 bar.c foo.c
161 $ hg status
161 $ hg status
162 ? bar.c
162 ? bar.c
163 ? foo.c
163 ? foo.c
164 $ hg add bar.c
164 $ hg add bar.c
165 $ hg status
165 $ hg status
166 A bar.c
166 A bar.c
167 ? foo.c
167 ? foo.c
168
168
169 Returns 0 if all files are successfully added.
169 Returns 0 if all files are successfully added.
170 """
170 """
171
171
172 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
172 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
173 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
173 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
174 return rejected and 1 or 0
174 return rejected and 1 or 0
175
175
176 @command('addremove',
176 @command('addremove',
177 similarityopts + subrepoopts + walkopts + dryrunopts,
177 similarityopts + subrepoopts + walkopts + dryrunopts,
178 _('[OPTION]... [FILE]...'),
178 _('[OPTION]... [FILE]...'),
179 inferrepo=True)
179 inferrepo=True)
180 def addremove(ui, repo, *pats, **opts):
180 def addremove(ui, repo, *pats, **opts):
181 """add all new files, delete all missing files
181 """add all new files, delete all missing files
182
182
183 Add all new files and remove all missing files from the
183 Add all new files and remove all missing files from the
184 repository.
184 repository.
185
185
186 Unless names are given, new files are ignored if they match any of
186 Unless names are given, new files are ignored if they match any of
187 the patterns in ``.hgignore``. As with add, these changes take
187 the patterns in ``.hgignore``. As with add, these changes take
188 effect at the next commit.
188 effect at the next commit.
189
189
190 Use the -s/--similarity option to detect renamed files. This
190 Use the -s/--similarity option to detect renamed files. This
191 option takes a percentage between 0 (disabled) and 100 (files must
191 option takes a percentage between 0 (disabled) and 100 (files must
192 be identical) as its parameter. With a parameter greater than 0,
192 be identical) as its parameter. With a parameter greater than 0,
193 this compares every removed file with every added file and records
193 this compares every removed file with every added file and records
194 those similar enough as renames. Detecting renamed files this way
194 those similar enough as renames. Detecting renamed files this way
195 can be expensive. After using this option, :hg:`status -C` can be
195 can be expensive. After using this option, :hg:`status -C` can be
196 used to check which files were identified as moved or renamed. If
196 used to check which files were identified as moved or renamed. If
197 not specified, -s/--similarity defaults to 100 and only renames of
197 not specified, -s/--similarity defaults to 100 and only renames of
198 identical files are detected.
198 identical files are detected.
199
199
200 .. container:: verbose
200 .. container:: verbose
201
201
202 Examples:
202 Examples:
203
203
204 - A number of files (bar.c and foo.c) are new,
204 - A number of files (bar.c and foo.c) are new,
205 while foobar.c has been removed (without using :hg:`remove`)
205 while foobar.c has been removed (without using :hg:`remove`)
206 from the repository::
206 from the repository::
207
207
208 $ ls
208 $ ls
209 bar.c foo.c
209 bar.c foo.c
210 $ hg status
210 $ hg status
211 ! foobar.c
211 ! foobar.c
212 ? bar.c
212 ? bar.c
213 ? foo.c
213 ? foo.c
214 $ hg addremove
214 $ hg addremove
215 adding bar.c
215 adding bar.c
216 adding foo.c
216 adding foo.c
217 removing foobar.c
217 removing foobar.c
218 $ hg status
218 $ hg status
219 A bar.c
219 A bar.c
220 A foo.c
220 A foo.c
221 R foobar.c
221 R foobar.c
222
222
223 - A file foobar.c was moved to foo.c without using :hg:`rename`.
223 - A file foobar.c was moved to foo.c without using :hg:`rename`.
224 Afterwards, it was edited slightly::
224 Afterwards, it was edited slightly::
225
225
226 $ ls
226 $ ls
227 foo.c
227 foo.c
228 $ hg status
228 $ hg status
229 ! foobar.c
229 ! foobar.c
230 ? foo.c
230 ? foo.c
231 $ hg addremove --similarity 90
231 $ hg addremove --similarity 90
232 removing foobar.c
232 removing foobar.c
233 adding foo.c
233 adding foo.c
234 recording removal of foobar.c as rename to foo.c (94% similar)
234 recording removal of foobar.c as rename to foo.c (94% similar)
235 $ hg status -C
235 $ hg status -C
236 A foo.c
236 A foo.c
237 foobar.c
237 foobar.c
238 R foobar.c
238 R foobar.c
239
239
240 Returns 0 if all files are successfully added.
240 Returns 0 if all files are successfully added.
241 """
241 """
242 opts = pycompat.byteskwargs(opts)
242 opts = pycompat.byteskwargs(opts)
243 try:
243 try:
244 sim = float(opts.get('similarity') or 100)
244 sim = float(opts.get('similarity') or 100)
245 except ValueError:
245 except ValueError:
246 raise error.Abort(_('similarity must be a number'))
246 raise error.Abort(_('similarity must be a number'))
247 if sim < 0 or sim > 100:
247 if sim < 0 or sim > 100:
248 raise error.Abort(_('similarity must be between 0 and 100'))
248 raise error.Abort(_('similarity must be between 0 and 100'))
249 matcher = scmutil.match(repo[None], pats, opts)
249 matcher = scmutil.match(repo[None], pats, opts)
250 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
250 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
251
251
252 @command('^annotate|blame',
252 @command('^annotate|blame',
253 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
253 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
254 ('', 'follow', None,
254 ('', 'follow', None,
255 _('follow copies/renames and list the filename (DEPRECATED)')),
255 _('follow copies/renames and list the filename (DEPRECATED)')),
256 ('', 'no-follow', None, _("don't follow copies and renames")),
256 ('', 'no-follow', None, _("don't follow copies and renames")),
257 ('a', 'text', None, _('treat all files as text')),
257 ('a', 'text', None, _('treat all files as text')),
258 ('u', 'user', None, _('list the author (long with -v)')),
258 ('u', 'user', None, _('list the author (long with -v)')),
259 ('f', 'file', None, _('list the filename')),
259 ('f', 'file', None, _('list the filename')),
260 ('d', 'date', None, _('list the date (short with -q)')),
260 ('d', 'date', None, _('list the date (short with -q)')),
261 ('n', 'number', None, _('list the revision number (default)')),
261 ('n', 'number', None, _('list the revision number (default)')),
262 ('c', 'changeset', None, _('list the changeset')),
262 ('c', 'changeset', None, _('list the changeset')),
263 ('l', 'line-number', None, _('show line number at the first appearance')),
263 ('l', 'line-number', None, _('show line number at the first appearance')),
264 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
264 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
265 ] + diffwsopts + walkopts + formatteropts,
265 ] + diffwsopts + walkopts + formatteropts,
266 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
266 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
267 inferrepo=True)
267 inferrepo=True)
268 def annotate(ui, repo, *pats, **opts):
268 def annotate(ui, repo, *pats, **opts):
269 """show changeset information by line for each file
269 """show changeset information by line for each file
270
270
271 List changes in files, showing the revision id responsible for
271 List changes in files, showing the revision id responsible for
272 each line.
272 each line.
273
273
274 This command is useful for discovering when a change was made and
274 This command is useful for discovering when a change was made and
275 by whom.
275 by whom.
276
276
277 If you include --file, --user, or --date, the revision number is
277 If you include --file, --user, or --date, the revision number is
278 suppressed unless you also include --number.
278 suppressed unless you also include --number.
279
279
280 Without the -a/--text option, annotate will avoid processing files
280 Without the -a/--text option, annotate will avoid processing files
281 it detects as binary. With -a, annotate will annotate the file
281 it detects as binary. With -a, annotate will annotate the file
282 anyway, although the results will probably be neither useful
282 anyway, although the results will probably be neither useful
283 nor desirable.
283 nor desirable.
284
284
285 Returns 0 on success.
285 Returns 0 on success.
286 """
286 """
287 opts = pycompat.byteskwargs(opts)
287 opts = pycompat.byteskwargs(opts)
288 if not pats:
288 if not pats:
289 raise error.Abort(_('at least one filename or pattern is required'))
289 raise error.Abort(_('at least one filename or pattern is required'))
290
290
291 if opts.get('follow'):
291 if opts.get('follow'):
292 # --follow is deprecated and now just an alias for -f/--file
292 # --follow is deprecated and now just an alias for -f/--file
293 # to mimic the behavior of Mercurial before version 1.5
293 # to mimic the behavior of Mercurial before version 1.5
294 opts['file'] = True
294 opts['file'] = True
295
295
296 rev = opts.get('rev')
296 rev = opts.get('rev')
297 if rev:
297 if rev:
298 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
298 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
299 ctx = scmutil.revsingle(repo, rev)
299 ctx = scmutil.revsingle(repo, rev)
300
300
301 rootfm = ui.formatter('annotate', opts)
301 rootfm = ui.formatter('annotate', opts)
302 if ui.quiet:
302 if ui.quiet:
303 datefunc = util.shortdate
303 datefunc = util.shortdate
304 else:
304 else:
305 datefunc = util.datestr
305 datefunc = util.datestr
306 if ctx.rev() is None:
306 if ctx.rev() is None:
307 def hexfn(node):
307 def hexfn(node):
308 if node is None:
308 if node is None:
309 return None
309 return None
310 else:
310 else:
311 return rootfm.hexfunc(node)
311 return rootfm.hexfunc(node)
312 if opts.get('changeset'):
312 if opts.get('changeset'):
313 # omit "+" suffix which is appended to node hex
313 # omit "+" suffix which is appended to node hex
314 def formatrev(rev):
314 def formatrev(rev):
315 if rev is None:
315 if rev is None:
316 return '%d' % ctx.p1().rev()
316 return '%d' % ctx.p1().rev()
317 else:
317 else:
318 return '%d' % rev
318 return '%d' % rev
319 else:
319 else:
320 def formatrev(rev):
320 def formatrev(rev):
321 if rev is None:
321 if rev is None:
322 return '%d+' % ctx.p1().rev()
322 return '%d+' % ctx.p1().rev()
323 else:
323 else:
324 return '%d ' % rev
324 return '%d ' % rev
325 def formathex(hex):
325 def formathex(hex):
326 if hex is None:
326 if hex is None:
327 return '%s+' % rootfm.hexfunc(ctx.p1().node())
327 return '%s+' % rootfm.hexfunc(ctx.p1().node())
328 else:
328 else:
329 return '%s ' % hex
329 return '%s ' % hex
330 else:
330 else:
331 hexfn = rootfm.hexfunc
331 hexfn = rootfm.hexfunc
332 formatrev = formathex = pycompat.bytestr
332 formatrev = formathex = pycompat.bytestr
333
333
334 opmap = [('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
334 opmap = [('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
335 ('number', ' ', lambda x: x.fctx.rev(), formatrev),
335 ('number', ' ', lambda x: x.fctx.rev(), formatrev),
336 ('changeset', ' ', lambda x: hexfn(x.fctx.node()), formathex),
336 ('changeset', ' ', lambda x: hexfn(x.fctx.node()), formathex),
337 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
337 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
338 ('file', ' ', lambda x: x.fctx.path(), str),
338 ('file', ' ', lambda x: x.fctx.path(), str),
339 ('line_number', ':', lambda x: x.lineno, str),
339 ('line_number', ':', lambda x: x.lineno, str),
340 ]
340 ]
341 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
341 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
342
342
343 if (not opts.get('user') and not opts.get('changeset')
343 if (not opts.get('user') and not opts.get('changeset')
344 and not opts.get('date') and not opts.get('file')):
344 and not opts.get('date') and not opts.get('file')):
345 opts['number'] = True
345 opts['number'] = True
346
346
347 linenumber = opts.get('line_number') is not None
347 linenumber = opts.get('line_number') is not None
348 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
348 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
349 raise error.Abort(_('at least one of -n/-c is required for -l'))
349 raise error.Abort(_('at least one of -n/-c is required for -l'))
350
350
351 ui.pager('annotate')
351 ui.pager('annotate')
352
352
353 if rootfm.isplain():
353 if rootfm.isplain():
354 def makefunc(get, fmt):
354 def makefunc(get, fmt):
355 return lambda x: fmt(get(x))
355 return lambda x: fmt(get(x))
356 else:
356 else:
357 def makefunc(get, fmt):
357 def makefunc(get, fmt):
358 return get
358 return get
359 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
359 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
360 if opts.get(op)]
360 if opts.get(op)]
361 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
361 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
362 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
362 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
363 if opts.get(op))
363 if opts.get(op))
364
364
365 def bad(x, y):
365 def bad(x, y):
366 raise error.Abort("%s: %s" % (x, y))
366 raise error.Abort("%s: %s" % (x, y))
367
367
368 m = scmutil.match(ctx, pats, opts, badfn=bad)
368 m = scmutil.match(ctx, pats, opts, badfn=bad)
369
369
370 follow = not opts.get('no_follow')
370 follow = not opts.get('no_follow')
371 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
371 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
372 whitespace=True)
372 whitespace=True)
373 skiprevs = opts.get('skip')
373 skiprevs = opts.get('skip')
374 if skiprevs:
374 if skiprevs:
375 skiprevs = scmutil.revrange(repo, skiprevs)
375 skiprevs = scmutil.revrange(repo, skiprevs)
376
376
377 for abs in ctx.walk(m):
377 for abs in ctx.walk(m):
378 fctx = ctx[abs]
378 fctx = ctx[abs]
379 rootfm.startitem()
379 rootfm.startitem()
380 rootfm.data(abspath=abs, path=m.rel(abs))
380 rootfm.data(abspath=abs, path=m.rel(abs))
381 if not opts.get('text') and fctx.isbinary():
381 if not opts.get('text') and fctx.isbinary():
382 rootfm.plain(_("%s: binary file\n")
382 rootfm.plain(_("%s: binary file\n")
383 % ((pats and m.rel(abs)) or abs))
383 % ((pats and m.rel(abs)) or abs))
384 continue
384 continue
385
385
386 fm = rootfm.nested('lines')
386 fm = rootfm.nested('lines')
387 lines = fctx.annotate(follow=follow, linenumber=linenumber,
387 lines = fctx.annotate(follow=follow, linenumber=linenumber,
388 skiprevs=skiprevs, diffopts=diffopts)
388 skiprevs=skiprevs, diffopts=diffopts)
389 if not lines:
389 if not lines:
390 fm.end()
390 fm.end()
391 continue
391 continue
392 formats = []
392 formats = []
393 pieces = []
393 pieces = []
394
394
395 for f, sep in funcmap:
395 for f, sep in funcmap:
396 l = [f(n) for n, dummy in lines]
396 l = [f(n) for n, dummy in lines]
397 if fm.isplain():
397 if fm.isplain():
398 sizes = [encoding.colwidth(x) for x in l]
398 sizes = [encoding.colwidth(x) for x in l]
399 ml = max(sizes)
399 ml = max(sizes)
400 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
400 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
401 else:
401 else:
402 formats.append(['%s' for x in l])
402 formats.append(['%s' for x in l])
403 pieces.append(l)
403 pieces.append(l)
404
404
405 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
405 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
406 fm.startitem()
406 fm.startitem()
407 fm.write(fields, "".join(f), *p)
407 fm.write(fields, "".join(f), *p)
408 if l[0].skip:
408 if l[0].skip:
409 fmt = "* %s"
409 fmt = "* %s"
410 else:
410 else:
411 fmt = ": %s"
411 fmt = ": %s"
412 fm.write('line', fmt, l[1])
412 fm.write('line', fmt, l[1])
413
413
414 if not lines[-1][1].endswith('\n'):
414 if not lines[-1][1].endswith('\n'):
415 fm.plain('\n')
415 fm.plain('\n')
416 fm.end()
416 fm.end()
417
417
418 rootfm.end()
418 rootfm.end()
419
419
420 @command('archive',
420 @command('archive',
421 [('', 'no-decode', None, _('do not pass files through decoders')),
421 [('', 'no-decode', None, _('do not pass files through decoders')),
422 ('p', 'prefix', '', _('directory prefix for files in archive'),
422 ('p', 'prefix', '', _('directory prefix for files in archive'),
423 _('PREFIX')),
423 _('PREFIX')),
424 ('r', 'rev', '', _('revision to distribute'), _('REV')),
424 ('r', 'rev', '', _('revision to distribute'), _('REV')),
425 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
425 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
426 ] + subrepoopts + walkopts,
426 ] + subrepoopts + walkopts,
427 _('[OPTION]... DEST'))
427 _('[OPTION]... DEST'))
428 def archive(ui, repo, dest, **opts):
428 def archive(ui, repo, dest, **opts):
429 '''create an unversioned archive of a repository revision
429 '''create an unversioned archive of a repository revision
430
430
431 By default, the revision used is the parent of the working
431 By default, the revision used is the parent of the working
432 directory; use -r/--rev to specify a different revision.
432 directory; use -r/--rev to specify a different revision.
433
433
434 The archive type is automatically detected based on file
434 The archive type is automatically detected based on file
435 extension (to override, use -t/--type).
435 extension (to override, use -t/--type).
436
436
437 .. container:: verbose
437 .. container:: verbose
438
438
439 Examples:
439 Examples:
440
440
441 - create a zip file containing the 1.0 release::
441 - create a zip file containing the 1.0 release::
442
442
443 hg archive -r 1.0 project-1.0.zip
443 hg archive -r 1.0 project-1.0.zip
444
444
445 - create a tarball excluding .hg files::
445 - create a tarball excluding .hg files::
446
446
447 hg archive project.tar.gz -X ".hg*"
447 hg archive project.tar.gz -X ".hg*"
448
448
449 Valid types are:
449 Valid types are:
450
450
451 :``files``: a directory full of files (default)
451 :``files``: a directory full of files (default)
452 :``tar``: tar archive, uncompressed
452 :``tar``: tar archive, uncompressed
453 :``tbz2``: tar archive, compressed using bzip2
453 :``tbz2``: tar archive, compressed using bzip2
454 :``tgz``: tar archive, compressed using gzip
454 :``tgz``: tar archive, compressed using gzip
455 :``uzip``: zip archive, uncompressed
455 :``uzip``: zip archive, uncompressed
456 :``zip``: zip archive, compressed using deflate
456 :``zip``: zip archive, compressed using deflate
457
457
458 The exact name of the destination archive or directory is given
458 The exact name of the destination archive or directory is given
459 using a format string; see :hg:`help export` for details.
459 using a format string; see :hg:`help export` for details.
460
460
461 Each member added to an archive file has a directory prefix
461 Each member added to an archive file has a directory prefix
462 prepended. Use -p/--prefix to specify a format string for the
462 prepended. Use -p/--prefix to specify a format string for the
463 prefix. The default is the basename of the archive, with suffixes
463 prefix. The default is the basename of the archive, with suffixes
464 removed.
464 removed.
465
465
466 Returns 0 on success.
466 Returns 0 on success.
467 '''
467 '''
468
468
469 opts = pycompat.byteskwargs(opts)
469 opts = pycompat.byteskwargs(opts)
470 rev = opts.get('rev')
470 rev = opts.get('rev')
471 if rev:
471 if rev:
472 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
472 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
473 ctx = scmutil.revsingle(repo, rev)
473 ctx = scmutil.revsingle(repo, rev)
474 if not ctx:
474 if not ctx:
475 raise error.Abort(_('no working directory: please specify a revision'))
475 raise error.Abort(_('no working directory: please specify a revision'))
476 node = ctx.node()
476 node = ctx.node()
477 dest = cmdutil.makefilename(repo, dest, node)
477 dest = cmdutil.makefilename(repo, dest, node)
478 if os.path.realpath(dest) == repo.root:
478 if os.path.realpath(dest) == repo.root:
479 raise error.Abort(_('repository root cannot be destination'))
479 raise error.Abort(_('repository root cannot be destination'))
480
480
481 kind = opts.get('type') or archival.guesskind(dest) or 'files'
481 kind = opts.get('type') or archival.guesskind(dest) or 'files'
482 prefix = opts.get('prefix')
482 prefix = opts.get('prefix')
483
483
484 if dest == '-':
484 if dest == '-':
485 if kind == 'files':
485 if kind == 'files':
486 raise error.Abort(_('cannot archive plain files to stdout'))
486 raise error.Abort(_('cannot archive plain files to stdout'))
487 dest = cmdutil.makefileobj(repo, dest)
487 dest = cmdutil.makefileobj(repo, dest)
488 if not prefix:
488 if not prefix:
489 prefix = os.path.basename(repo.root) + '-%h'
489 prefix = os.path.basename(repo.root) + '-%h'
490
490
491 prefix = cmdutil.makefilename(repo, prefix, node)
491 prefix = cmdutil.makefilename(repo, prefix, node)
492 match = scmutil.match(ctx, [], opts)
492 match = scmutil.match(ctx, [], opts)
493 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
493 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
494 match, prefix, subrepos=opts.get('subrepos'))
494 match, prefix, subrepos=opts.get('subrepos'))
495
495
496 @command('backout',
496 @command('backout',
497 [('', 'merge', None, _('merge with old dirstate parent after backout')),
497 [('', 'merge', None, _('merge with old dirstate parent after backout')),
498 ('', 'commit', None,
498 ('', 'commit', None,
499 _('commit if no conflicts were encountered (DEPRECATED)')),
499 _('commit if no conflicts were encountered (DEPRECATED)')),
500 ('', 'no-commit', None, _('do not commit')),
500 ('', 'no-commit', None, _('do not commit')),
501 ('', 'parent', '',
501 ('', 'parent', '',
502 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
502 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
503 ('r', 'rev', '', _('revision to backout'), _('REV')),
503 ('r', 'rev', '', _('revision to backout'), _('REV')),
504 ('e', 'edit', False, _('invoke editor on commit messages')),
504 ('e', 'edit', False, _('invoke editor on commit messages')),
505 ] + mergetoolopts + walkopts + commitopts + commitopts2,
505 ] + mergetoolopts + walkopts + commitopts + commitopts2,
506 _('[OPTION]... [-r] REV'))
506 _('[OPTION]... [-r] REV'))
507 def backout(ui, repo, node=None, rev=None, **opts):
507 def backout(ui, repo, node=None, rev=None, **opts):
508 '''reverse effect of earlier changeset
508 '''reverse effect of earlier changeset
509
509
510 Prepare a new changeset with the effect of REV undone in the
510 Prepare a new changeset with the effect of REV undone in the
511 current working directory. If no conflicts were encountered,
511 current working directory. If no conflicts were encountered,
512 it will be committed immediately.
512 it will be committed immediately.
513
513
514 If REV is the parent of the working directory, then this new changeset
514 If REV is the parent of the working directory, then this new changeset
515 is committed automatically (unless --no-commit is specified).
515 is committed automatically (unless --no-commit is specified).
516
516
517 .. note::
517 .. note::
518
518
519 :hg:`backout` cannot be used to fix either an unwanted or
519 :hg:`backout` cannot be used to fix either an unwanted or
520 incorrect merge.
520 incorrect merge.
521
521
522 .. container:: verbose
522 .. container:: verbose
523
523
524 Examples:
524 Examples:
525
525
526 - Reverse the effect of the parent of the working directory.
526 - Reverse the effect of the parent of the working directory.
527 This backout will be committed immediately::
527 This backout will be committed immediately::
528
528
529 hg backout -r .
529 hg backout -r .
530
530
531 - Reverse the effect of previous bad revision 23::
531 - Reverse the effect of previous bad revision 23::
532
532
533 hg backout -r 23
533 hg backout -r 23
534
534
535 - Reverse the effect of previous bad revision 23 and
535 - Reverse the effect of previous bad revision 23 and
536 leave changes uncommitted::
536 leave changes uncommitted::
537
537
538 hg backout -r 23 --no-commit
538 hg backout -r 23 --no-commit
539 hg commit -m "Backout revision 23"
539 hg commit -m "Backout revision 23"
540
540
541 By default, the pending changeset will have one parent,
541 By default, the pending changeset will have one parent,
542 maintaining a linear history. With --merge, the pending
542 maintaining a linear history. With --merge, the pending
543 changeset will instead have two parents: the old parent of the
543 changeset will instead have two parents: the old parent of the
544 working directory and a new child of REV that simply undoes REV.
544 working directory and a new child of REV that simply undoes REV.
545
545
546 Before version 1.7, the behavior without --merge was equivalent
546 Before version 1.7, the behavior without --merge was equivalent
547 to specifying --merge followed by :hg:`update --clean .` to
547 to specifying --merge followed by :hg:`update --clean .` to
548 cancel the merge and leave the child of REV as a head to be
548 cancel the merge and leave the child of REV as a head to be
549 merged separately.
549 merged separately.
550
550
551 See :hg:`help dates` for a list of formats valid for -d/--date.
551 See :hg:`help dates` for a list of formats valid for -d/--date.
552
552
553 See :hg:`help revert` for a way to restore files to the state
553 See :hg:`help revert` for a way to restore files to the state
554 of another revision.
554 of another revision.
555
555
556 Returns 0 on success, 1 if nothing to backout or there are unresolved
556 Returns 0 on success, 1 if nothing to backout or there are unresolved
557 files.
557 files.
558 '''
558 '''
559 wlock = lock = None
559 wlock = lock = None
560 try:
560 try:
561 wlock = repo.wlock()
561 wlock = repo.wlock()
562 lock = repo.lock()
562 lock = repo.lock()
563 return _dobackout(ui, repo, node, rev, **opts)
563 return _dobackout(ui, repo, node, rev, **opts)
564 finally:
564 finally:
565 release(lock, wlock)
565 release(lock, wlock)
566
566
567 def _dobackout(ui, repo, node=None, rev=None, **opts):
567 def _dobackout(ui, repo, node=None, rev=None, **opts):
568 opts = pycompat.byteskwargs(opts)
568 opts = pycompat.byteskwargs(opts)
569 if opts.get('commit') and opts.get('no_commit'):
569 if opts.get('commit') and opts.get('no_commit'):
570 raise error.Abort(_("cannot use --commit with --no-commit"))
570 raise error.Abort(_("cannot use --commit with --no-commit"))
571 if opts.get('merge') and opts.get('no_commit'):
571 if opts.get('merge') and opts.get('no_commit'):
572 raise error.Abort(_("cannot use --merge with --no-commit"))
572 raise error.Abort(_("cannot use --merge with --no-commit"))
573
573
574 if rev and node:
574 if rev and node:
575 raise error.Abort(_("please specify just one revision"))
575 raise error.Abort(_("please specify just one revision"))
576
576
577 if not rev:
577 if not rev:
578 rev = node
578 rev = node
579
579
580 if not rev:
580 if not rev:
581 raise error.Abort(_("please specify a revision to backout"))
581 raise error.Abort(_("please specify a revision to backout"))
582
582
583 date = opts.get('date')
583 date = opts.get('date')
584 if date:
584 if date:
585 opts['date'] = util.parsedate(date)
585 opts['date'] = util.parsedate(date)
586
586
587 cmdutil.checkunfinished(repo)
587 cmdutil.checkunfinished(repo)
588 cmdutil.bailifchanged(repo)
588 cmdutil.bailifchanged(repo)
589 node = scmutil.revsingle(repo, rev).node()
589 node = scmutil.revsingle(repo, rev).node()
590
590
591 op1, op2 = repo.dirstate.parents()
591 op1, op2 = repo.dirstate.parents()
592 if not repo.changelog.isancestor(node, op1):
592 if not repo.changelog.isancestor(node, op1):
593 raise error.Abort(_('cannot backout change that is not an ancestor'))
593 raise error.Abort(_('cannot backout change that is not an ancestor'))
594
594
595 p1, p2 = repo.changelog.parents(node)
595 p1, p2 = repo.changelog.parents(node)
596 if p1 == nullid:
596 if p1 == nullid:
597 raise error.Abort(_('cannot backout a change with no parents'))
597 raise error.Abort(_('cannot backout a change with no parents'))
598 if p2 != nullid:
598 if p2 != nullid:
599 if not opts.get('parent'):
599 if not opts.get('parent'):
600 raise error.Abort(_('cannot backout a merge changeset'))
600 raise error.Abort(_('cannot backout a merge changeset'))
601 p = repo.lookup(opts['parent'])
601 p = repo.lookup(opts['parent'])
602 if p not in (p1, p2):
602 if p not in (p1, p2):
603 raise error.Abort(_('%s is not a parent of %s') %
603 raise error.Abort(_('%s is not a parent of %s') %
604 (short(p), short(node)))
604 (short(p), short(node)))
605 parent = p
605 parent = p
606 else:
606 else:
607 if opts.get('parent'):
607 if opts.get('parent'):
608 raise error.Abort(_('cannot use --parent on non-merge changeset'))
608 raise error.Abort(_('cannot use --parent on non-merge changeset'))
609 parent = p1
609 parent = p1
610
610
611 # the backout should appear on the same branch
611 # the backout should appear on the same branch
612 branch = repo.dirstate.branch()
612 branch = repo.dirstate.branch()
613 bheads = repo.branchheads(branch)
613 bheads = repo.branchheads(branch)
614 rctx = scmutil.revsingle(repo, hex(parent))
614 rctx = scmutil.revsingle(repo, hex(parent))
615 if not opts.get('merge') and op1 != node:
615 if not opts.get('merge') and op1 != node:
616 dsguard = dirstateguard.dirstateguard(repo, 'backout')
616 dsguard = dirstateguard.dirstateguard(repo, 'backout')
617 try:
617 try:
618 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
618 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
619 'backout')
619 'backout')
620 stats = mergemod.update(repo, parent, True, True, node, False)
620 stats = mergemod.update(repo, parent, True, True, node, False)
621 repo.setparents(op1, op2)
621 repo.setparents(op1, op2)
622 dsguard.close()
622 dsguard.close()
623 hg._showstats(repo, stats)
623 hg._showstats(repo, stats)
624 if stats[3]:
624 if stats[3]:
625 repo.ui.status(_("use 'hg resolve' to retry unresolved "
625 repo.ui.status(_("use 'hg resolve' to retry unresolved "
626 "file merges\n"))
626 "file merges\n"))
627 return 1
627 return 1
628 finally:
628 finally:
629 ui.setconfig('ui', 'forcemerge', '', '')
629 ui.setconfig('ui', 'forcemerge', '', '')
630 lockmod.release(dsguard)
630 lockmod.release(dsguard)
631 else:
631 else:
632 hg.clean(repo, node, show_stats=False)
632 hg.clean(repo, node, show_stats=False)
633 repo.dirstate.setbranch(branch)
633 repo.dirstate.setbranch(branch)
634 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
634 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
635
635
636 if opts.get('no_commit'):
636 if opts.get('no_commit'):
637 msg = _("changeset %s backed out, "
637 msg = _("changeset %s backed out, "
638 "don't forget to commit.\n")
638 "don't forget to commit.\n")
639 ui.status(msg % short(node))
639 ui.status(msg % short(node))
640 return 0
640 return 0
641
641
642 def commitfunc(ui, repo, message, match, opts):
642 def commitfunc(ui, repo, message, match, opts):
643 editform = 'backout'
643 editform = 'backout'
644 e = cmdutil.getcommiteditor(editform=editform,
644 e = cmdutil.getcommiteditor(editform=editform,
645 **pycompat.strkwargs(opts))
645 **pycompat.strkwargs(opts))
646 if not message:
646 if not message:
647 # we don't translate commit messages
647 # we don't translate commit messages
648 message = "Backed out changeset %s" % short(node)
648 message = "Backed out changeset %s" % short(node)
649 e = cmdutil.getcommiteditor(edit=True, editform=editform)
649 e = cmdutil.getcommiteditor(edit=True, editform=editform)
650 return repo.commit(message, opts.get('user'), opts.get('date'),
650 return repo.commit(message, opts.get('user'), opts.get('date'),
651 match, editor=e)
651 match, editor=e)
652 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
652 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
653 if not newnode:
653 if not newnode:
654 ui.status(_("nothing changed\n"))
654 ui.status(_("nothing changed\n"))
655 return 1
655 return 1
656 cmdutil.commitstatus(repo, newnode, branch, bheads)
656 cmdutil.commitstatus(repo, newnode, branch, bheads)
657
657
658 def nice(node):
658 def nice(node):
659 return '%d:%s' % (repo.changelog.rev(node), short(node))
659 return '%d:%s' % (repo.changelog.rev(node), short(node))
660 ui.status(_('changeset %s backs out changeset %s\n') %
660 ui.status(_('changeset %s backs out changeset %s\n') %
661 (nice(repo.changelog.tip()), nice(node)))
661 (nice(repo.changelog.tip()), nice(node)))
662 if opts.get('merge') and op1 != node:
662 if opts.get('merge') and op1 != node:
663 hg.clean(repo, op1, show_stats=False)
663 hg.clean(repo, op1, show_stats=False)
664 ui.status(_('merging with changeset %s\n')
664 ui.status(_('merging with changeset %s\n')
665 % nice(repo.changelog.tip()))
665 % nice(repo.changelog.tip()))
666 try:
666 try:
667 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
667 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
668 'backout')
668 'backout')
669 return hg.merge(repo, hex(repo.changelog.tip()))
669 return hg.merge(repo, hex(repo.changelog.tip()))
670 finally:
670 finally:
671 ui.setconfig('ui', 'forcemerge', '', '')
671 ui.setconfig('ui', 'forcemerge', '', '')
672 return 0
672 return 0
673
673
674 @command('bisect',
674 @command('bisect',
675 [('r', 'reset', False, _('reset bisect state')),
675 [('r', 'reset', False, _('reset bisect state')),
676 ('g', 'good', False, _('mark changeset good')),
676 ('g', 'good', False, _('mark changeset good')),
677 ('b', 'bad', False, _('mark changeset bad')),
677 ('b', 'bad', False, _('mark changeset bad')),
678 ('s', 'skip', False, _('skip testing changeset')),
678 ('s', 'skip', False, _('skip testing changeset')),
679 ('e', 'extend', False, _('extend the bisect range')),
679 ('e', 'extend', False, _('extend the bisect range')),
680 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
680 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
681 ('U', 'noupdate', False, _('do not update to target'))],
681 ('U', 'noupdate', False, _('do not update to target'))],
682 _("[-gbsr] [-U] [-c CMD] [REV]"))
682 _("[-gbsr] [-U] [-c CMD] [REV]"))
683 def bisect(ui, repo, rev=None, extra=None, command=None,
683 def bisect(ui, repo, rev=None, extra=None, command=None,
684 reset=None, good=None, bad=None, skip=None, extend=None,
684 reset=None, good=None, bad=None, skip=None, extend=None,
685 noupdate=None):
685 noupdate=None):
686 """subdivision search of changesets
686 """subdivision search of changesets
687
687
688 This command helps to find changesets which introduce problems. To
688 This command helps to find changesets which introduce problems. To
689 use, mark the earliest changeset you know exhibits the problem as
689 use, mark the earliest changeset you know exhibits the problem as
690 bad, then mark the latest changeset which is free from the problem
690 bad, then mark the latest changeset which is free from the problem
691 as good. Bisect will update your working directory to a revision
691 as good. Bisect will update your working directory to a revision
692 for testing (unless the -U/--noupdate option is specified). Once
692 for testing (unless the -U/--noupdate option is specified). Once
693 you have performed tests, mark the working directory as good or
693 you have performed tests, mark the working directory as good or
694 bad, and bisect will either update to another candidate changeset
694 bad, and bisect will either update to another candidate changeset
695 or announce that it has found the bad revision.
695 or announce that it has found the bad revision.
696
696
697 As a shortcut, you can also use the revision argument to mark a
697 As a shortcut, you can also use the revision argument to mark a
698 revision as good or bad without checking it out first.
698 revision as good or bad without checking it out first.
699
699
700 If you supply a command, it will be used for automatic bisection.
700 If you supply a command, it will be used for automatic bisection.
701 The environment variable HG_NODE will contain the ID of the
701 The environment variable HG_NODE will contain the ID of the
702 changeset being tested. The exit status of the command will be
702 changeset being tested. The exit status of the command will be
703 used to mark revisions as good or bad: status 0 means good, 125
703 used to mark revisions as good or bad: status 0 means good, 125
704 means to skip the revision, 127 (command not found) will abort the
704 means to skip the revision, 127 (command not found) will abort the
705 bisection, and any other non-zero exit status means the revision
705 bisection, and any other non-zero exit status means the revision
706 is bad.
706 is bad.
707
707
708 .. container:: verbose
708 .. container:: verbose
709
709
710 Some examples:
710 Some examples:
711
711
712 - start a bisection with known bad revision 34, and good revision 12::
712 - start a bisection with known bad revision 34, and good revision 12::
713
713
714 hg bisect --bad 34
714 hg bisect --bad 34
715 hg bisect --good 12
715 hg bisect --good 12
716
716
717 - advance the current bisection by marking current revision as good or
717 - advance the current bisection by marking current revision as good or
718 bad::
718 bad::
719
719
720 hg bisect --good
720 hg bisect --good
721 hg bisect --bad
721 hg bisect --bad
722
722
723 - mark the current revision, or a known revision, to be skipped (e.g. if
723 - mark the current revision, or a known revision, to be skipped (e.g. if
724 that revision is not usable because of another issue)::
724 that revision is not usable because of another issue)::
725
725
726 hg bisect --skip
726 hg bisect --skip
727 hg bisect --skip 23
727 hg bisect --skip 23
728
728
729 - skip all revisions that do not touch directories ``foo`` or ``bar``::
729 - skip all revisions that do not touch directories ``foo`` or ``bar``::
730
730
731 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
731 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
732
732
733 - forget the current bisection::
733 - forget the current bisection::
734
734
735 hg bisect --reset
735 hg bisect --reset
736
736
737 - use 'make && make tests' to automatically find the first broken
737 - use 'make && make tests' to automatically find the first broken
738 revision::
738 revision::
739
739
740 hg bisect --reset
740 hg bisect --reset
741 hg bisect --bad 34
741 hg bisect --bad 34
742 hg bisect --good 12
742 hg bisect --good 12
743 hg bisect --command "make && make tests"
743 hg bisect --command "make && make tests"
744
744
745 - see all changesets whose states are already known in the current
745 - see all changesets whose states are already known in the current
746 bisection::
746 bisection::
747
747
748 hg log -r "bisect(pruned)"
748 hg log -r "bisect(pruned)"
749
749
750 - see the changeset currently being bisected (especially useful
750 - see the changeset currently being bisected (especially useful
751 if running with -U/--noupdate)::
751 if running with -U/--noupdate)::
752
752
753 hg log -r "bisect(current)"
753 hg log -r "bisect(current)"
754
754
755 - see all changesets that took part in the current bisection::
755 - see all changesets that took part in the current bisection::
756
756
757 hg log -r "bisect(range)"
757 hg log -r "bisect(range)"
758
758
759 - you can even get a nice graph::
759 - you can even get a nice graph::
760
760
761 hg log --graph -r "bisect(range)"
761 hg log --graph -r "bisect(range)"
762
762
763 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
763 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
764
764
765 Returns 0 on success.
765 Returns 0 on success.
766 """
766 """
767 # backward compatibility
767 # backward compatibility
768 if rev in "good bad reset init".split():
768 if rev in "good bad reset init".split():
769 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
769 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
770 cmd, rev, extra = rev, extra, None
770 cmd, rev, extra = rev, extra, None
771 if cmd == "good":
771 if cmd == "good":
772 good = True
772 good = True
773 elif cmd == "bad":
773 elif cmd == "bad":
774 bad = True
774 bad = True
775 else:
775 else:
776 reset = True
776 reset = True
777 elif extra:
777 elif extra:
778 raise error.Abort(_('incompatible arguments'))
778 raise error.Abort(_('incompatible arguments'))
779
779
780 incompatibles = {
780 incompatibles = {
781 '--bad': bad,
781 '--bad': bad,
782 '--command': bool(command),
782 '--command': bool(command),
783 '--extend': extend,
783 '--extend': extend,
784 '--good': good,
784 '--good': good,
785 '--reset': reset,
785 '--reset': reset,
786 '--skip': skip,
786 '--skip': skip,
787 }
787 }
788
788
789 enabled = [x for x in incompatibles if incompatibles[x]]
789 enabled = [x for x in incompatibles if incompatibles[x]]
790
790
791 if len(enabled) > 1:
791 if len(enabled) > 1:
792 raise error.Abort(_('%s and %s are incompatible') %
792 raise error.Abort(_('%s and %s are incompatible') %
793 tuple(sorted(enabled)[0:2]))
793 tuple(sorted(enabled)[0:2]))
794
794
795 if reset:
795 if reset:
796 hbisect.resetstate(repo)
796 hbisect.resetstate(repo)
797 return
797 return
798
798
799 state = hbisect.load_state(repo)
799 state = hbisect.load_state(repo)
800
800
801 # update state
801 # update state
802 if good or bad or skip:
802 if good or bad or skip:
803 if rev:
803 if rev:
804 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
804 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
805 else:
805 else:
806 nodes = [repo.lookup('.')]
806 nodes = [repo.lookup('.')]
807 if good:
807 if good:
808 state['good'] += nodes
808 state['good'] += nodes
809 elif bad:
809 elif bad:
810 state['bad'] += nodes
810 state['bad'] += nodes
811 elif skip:
811 elif skip:
812 state['skip'] += nodes
812 state['skip'] += nodes
813 hbisect.save_state(repo, state)
813 hbisect.save_state(repo, state)
814 if not (state['good'] and state['bad']):
814 if not (state['good'] and state['bad']):
815 return
815 return
816
816
817 def mayupdate(repo, node, show_stats=True):
817 def mayupdate(repo, node, show_stats=True):
818 """common used update sequence"""
818 """common used update sequence"""
819 if noupdate:
819 if noupdate:
820 return
820 return
821 cmdutil.checkunfinished(repo)
821 cmdutil.checkunfinished(repo)
822 cmdutil.bailifchanged(repo)
822 cmdutil.bailifchanged(repo)
823 return hg.clean(repo, node, show_stats=show_stats)
823 return hg.clean(repo, node, show_stats=show_stats)
824
824
825 displayer = cmdutil.show_changeset(ui, repo, {})
825 displayer = cmdutil.show_changeset(ui, repo, {})
826
826
827 if command:
827 if command:
828 changesets = 1
828 changesets = 1
829 if noupdate:
829 if noupdate:
830 try:
830 try:
831 node = state['current'][0]
831 node = state['current'][0]
832 except LookupError:
832 except LookupError:
833 raise error.Abort(_('current bisect revision is unknown - '
833 raise error.Abort(_('current bisect revision is unknown - '
834 'start a new bisect to fix'))
834 'start a new bisect to fix'))
835 else:
835 else:
836 node, p2 = repo.dirstate.parents()
836 node, p2 = repo.dirstate.parents()
837 if p2 != nullid:
837 if p2 != nullid:
838 raise error.Abort(_('current bisect revision is a merge'))
838 raise error.Abort(_('current bisect revision is a merge'))
839 if rev:
839 if rev:
840 node = repo[scmutil.revsingle(repo, rev, node)].node()
840 node = repo[scmutil.revsingle(repo, rev, node)].node()
841 try:
841 try:
842 while changesets:
842 while changesets:
843 # update state
843 # update state
844 state['current'] = [node]
844 state['current'] = [node]
845 hbisect.save_state(repo, state)
845 hbisect.save_state(repo, state)
846 status = ui.system(command, environ={'HG_NODE': hex(node)},
846 status = ui.system(command, environ={'HG_NODE': hex(node)},
847 blockedtag='bisect_check')
847 blockedtag='bisect_check')
848 if status == 125:
848 if status == 125:
849 transition = "skip"
849 transition = "skip"
850 elif status == 0:
850 elif status == 0:
851 transition = "good"
851 transition = "good"
852 # status < 0 means process was killed
852 # status < 0 means process was killed
853 elif status == 127:
853 elif status == 127:
854 raise error.Abort(_("failed to execute %s") % command)
854 raise error.Abort(_("failed to execute %s") % command)
855 elif status < 0:
855 elif status < 0:
856 raise error.Abort(_("%s killed") % command)
856 raise error.Abort(_("%s killed") % command)
857 else:
857 else:
858 transition = "bad"
858 transition = "bad"
859 state[transition].append(node)
859 state[transition].append(node)
860 ctx = repo[node]
860 ctx = repo[node]
861 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
861 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
862 hbisect.checkstate(state)
862 hbisect.checkstate(state)
863 # bisect
863 # bisect
864 nodes, changesets, bgood = hbisect.bisect(repo, state)
864 nodes, changesets, bgood = hbisect.bisect(repo, state)
865 # update to next check
865 # update to next check
866 node = nodes[0]
866 node = nodes[0]
867 mayupdate(repo, node, show_stats=False)
867 mayupdate(repo, node, show_stats=False)
868 finally:
868 finally:
869 state['current'] = [node]
869 state['current'] = [node]
870 hbisect.save_state(repo, state)
870 hbisect.save_state(repo, state)
871 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
871 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
872 return
872 return
873
873
874 hbisect.checkstate(state)
874 hbisect.checkstate(state)
875
875
876 # actually bisect
876 # actually bisect
877 nodes, changesets, good = hbisect.bisect(repo, state)
877 nodes, changesets, good = hbisect.bisect(repo, state)
878 if extend:
878 if extend:
879 if not changesets:
879 if not changesets:
880 extendnode = hbisect.extendrange(repo, state, nodes, good)
880 extendnode = hbisect.extendrange(repo, state, nodes, good)
881 if extendnode is not None:
881 if extendnode is not None:
882 ui.write(_("Extending search to changeset %d:%s\n")
882 ui.write(_("Extending search to changeset %d:%s\n")
883 % (extendnode.rev(), extendnode))
883 % (extendnode.rev(), extendnode))
884 state['current'] = [extendnode.node()]
884 state['current'] = [extendnode.node()]
885 hbisect.save_state(repo, state)
885 hbisect.save_state(repo, state)
886 return mayupdate(repo, extendnode.node())
886 return mayupdate(repo, extendnode.node())
887 raise error.Abort(_("nothing to extend"))
887 raise error.Abort(_("nothing to extend"))
888
888
889 if changesets == 0:
889 if changesets == 0:
890 hbisect.printresult(ui, repo, state, displayer, nodes, good)
890 hbisect.printresult(ui, repo, state, displayer, nodes, good)
891 else:
891 else:
892 assert len(nodes) == 1 # only a single node can be tested next
892 assert len(nodes) == 1 # only a single node can be tested next
893 node = nodes[0]
893 node = nodes[0]
894 # compute the approximate number of remaining tests
894 # compute the approximate number of remaining tests
895 tests, size = 0, 2
895 tests, size = 0, 2
896 while size <= changesets:
896 while size <= changesets:
897 tests, size = tests + 1, size * 2
897 tests, size = tests + 1, size * 2
898 rev = repo.changelog.rev(node)
898 rev = repo.changelog.rev(node)
899 ui.write(_("Testing changeset %d:%s "
899 ui.write(_("Testing changeset %d:%s "
900 "(%d changesets remaining, ~%d tests)\n")
900 "(%d changesets remaining, ~%d tests)\n")
901 % (rev, short(node), changesets, tests))
901 % (rev, short(node), changesets, tests))
902 state['current'] = [node]
902 state['current'] = [node]
903 hbisect.save_state(repo, state)
903 hbisect.save_state(repo, state)
904 return mayupdate(repo, node)
904 return mayupdate(repo, node)
905
905
906 @command('bookmarks|bookmark',
906 @command('bookmarks|bookmark',
907 [('f', 'force', False, _('force')),
907 [('f', 'force', False, _('force')),
908 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
908 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
909 ('d', 'delete', False, _('delete a given bookmark')),
909 ('d', 'delete', False, _('delete a given bookmark')),
910 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
910 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
911 ('i', 'inactive', False, _('mark a bookmark inactive')),
911 ('i', 'inactive', False, _('mark a bookmark inactive')),
912 ] + formatteropts,
912 ] + formatteropts,
913 _('hg bookmarks [OPTIONS]... [NAME]...'))
913 _('hg bookmarks [OPTIONS]... [NAME]...'))
914 def bookmark(ui, repo, *names, **opts):
914 def bookmark(ui, repo, *names, **opts):
915 '''create a new bookmark or list existing bookmarks
915 '''create a new bookmark or list existing bookmarks
916
916
917 Bookmarks are labels on changesets to help track lines of development.
917 Bookmarks are labels on changesets to help track lines of development.
918 Bookmarks are unversioned and can be moved, renamed and deleted.
918 Bookmarks are unversioned and can be moved, renamed and deleted.
919 Deleting or moving a bookmark has no effect on the associated changesets.
919 Deleting or moving a bookmark has no effect on the associated changesets.
920
920
921 Creating or updating to a bookmark causes it to be marked as 'active'.
921 Creating or updating to a bookmark causes it to be marked as 'active'.
922 The active bookmark is indicated with a '*'.
922 The active bookmark is indicated with a '*'.
923 When a commit is made, the active bookmark will advance to the new commit.
923 When a commit is made, the active bookmark will advance to the new commit.
924 A plain :hg:`update` will also advance an active bookmark, if possible.
924 A plain :hg:`update` will also advance an active bookmark, if possible.
925 Updating away from a bookmark will cause it to be deactivated.
925 Updating away from a bookmark will cause it to be deactivated.
926
926
927 Bookmarks can be pushed and pulled between repositories (see
927 Bookmarks can be pushed and pulled between repositories (see
928 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
928 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
929 diverged, a new 'divergent bookmark' of the form 'name@path' will
929 diverged, a new 'divergent bookmark' of the form 'name@path' will
930 be created. Using :hg:`merge` will resolve the divergence.
930 be created. Using :hg:`merge` will resolve the divergence.
931
931
932 Specifying bookmark as '.' to -m or -d options is equivalent to specifying
932 Specifying bookmark as '.' to -m or -d options is equivalent to specifying
933 the active bookmark's name.
933 the active bookmark's name.
934
934
935 A bookmark named '@' has the special property that :hg:`clone` will
935 A bookmark named '@' has the special property that :hg:`clone` will
936 check it out by default if it exists.
936 check it out by default if it exists.
937
937
938 .. container:: verbose
938 .. container:: verbose
939
939
940 Examples:
940 Examples:
941
941
942 - create an active bookmark for a new line of development::
942 - create an active bookmark for a new line of development::
943
943
944 hg book new-feature
944 hg book new-feature
945
945
946 - create an inactive bookmark as a place marker::
946 - create an inactive bookmark as a place marker::
947
947
948 hg book -i reviewed
948 hg book -i reviewed
949
949
950 - create an inactive bookmark on another changeset::
950 - create an inactive bookmark on another changeset::
951
951
952 hg book -r .^ tested
952 hg book -r .^ tested
953
953
954 - rename bookmark turkey to dinner::
954 - rename bookmark turkey to dinner::
955
955
956 hg book -m turkey dinner
956 hg book -m turkey dinner
957
957
958 - move the '@' bookmark from another branch::
958 - move the '@' bookmark from another branch::
959
959
960 hg book -f @
960 hg book -f @
961 '''
961 '''
962 force = opts.get(r'force')
962 force = opts.get(r'force')
963 rev = opts.get(r'rev')
963 rev = opts.get(r'rev')
964 delete = opts.get(r'delete')
964 delete = opts.get(r'delete')
965 rename = opts.get(r'rename')
965 rename = opts.get(r'rename')
966 inactive = opts.get(r'inactive')
966 inactive = opts.get(r'inactive')
967
967
968 if delete and rename:
968 if delete and rename:
969 raise error.Abort(_("--delete and --rename are incompatible"))
969 raise error.Abort(_("--delete and --rename are incompatible"))
970 if delete and rev:
970 if delete and rev:
971 raise error.Abort(_("--rev is incompatible with --delete"))
971 raise error.Abort(_("--rev is incompatible with --delete"))
972 if rename and rev:
972 if rename and rev:
973 raise error.Abort(_("--rev is incompatible with --rename"))
973 raise error.Abort(_("--rev is incompatible with --rename"))
974 if not names and (delete or rev):
974 if not names and (delete or rev):
975 raise error.Abort(_("bookmark name required"))
975 raise error.Abort(_("bookmark name required"))
976
976
977 if delete or rename or names or inactive:
977 if delete or rename or names or inactive:
978 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
978 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
979 if delete:
979 if delete:
980 names = pycompat.maplist(repo._bookmarks.expandname, names)
980 names = pycompat.maplist(repo._bookmarks.expandname, names)
981 bookmarks.delete(repo, tr, names)
981 bookmarks.delete(repo, tr, names)
982 elif rename:
982 elif rename:
983 if not names:
983 if not names:
984 raise error.Abort(_("new bookmark name required"))
984 raise error.Abort(_("new bookmark name required"))
985 elif len(names) > 1:
985 elif len(names) > 1:
986 raise error.Abort(_("only one new bookmark name allowed"))
986 raise error.Abort(_("only one new bookmark name allowed"))
987 rename = repo._bookmarks.expandname(rename)
987 rename = repo._bookmarks.expandname(rename)
988 bookmarks.rename(repo, tr, rename, names[0], force, inactive)
988 bookmarks.rename(repo, tr, rename, names[0], force, inactive)
989 elif names:
989 elif names:
990 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
990 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
991 elif inactive:
991 elif inactive:
992 if len(repo._bookmarks) == 0:
992 if len(repo._bookmarks) == 0:
993 ui.status(_("no bookmarks set\n"))
993 ui.status(_("no bookmarks set\n"))
994 elif not repo._activebookmark:
994 elif not repo._activebookmark:
995 ui.status(_("no active bookmark\n"))
995 ui.status(_("no active bookmark\n"))
996 else:
996 else:
997 bookmarks.deactivate(repo)
997 bookmarks.deactivate(repo)
998 else: # show bookmarks
998 else: # show bookmarks
999 bookmarks.printbookmarks(ui, repo, **opts)
999 bookmarks.printbookmarks(ui, repo, **opts)
1000
1000
1001 @command('branch',
1001 @command('branch',
1002 [('f', 'force', None,
1002 [('f', 'force', None,
1003 _('set branch name even if it shadows an existing branch')),
1003 _('set branch name even if it shadows an existing branch')),
1004 ('C', 'clean', None, _('reset branch name to parent branch name'))],
1004 ('C', 'clean', None, _('reset branch name to parent branch name'))],
1005 _('[-fC] [NAME]'))
1005 _('[-fC] [NAME]'))
1006 def branch(ui, repo, label=None, **opts):
1006 def branch(ui, repo, label=None, **opts):
1007 """set or show the current branch name
1007 """set or show the current branch name
1008
1008
1009 .. note::
1009 .. note::
1010
1010
1011 Branch names are permanent and global. Use :hg:`bookmark` to create a
1011 Branch names are permanent and global. Use :hg:`bookmark` to create a
1012 light-weight bookmark instead. See :hg:`help glossary` for more
1012 light-weight bookmark instead. See :hg:`help glossary` for more
1013 information about named branches and bookmarks.
1013 information about named branches and bookmarks.
1014
1014
1015 With no argument, show the current branch name. With one argument,
1015 With no argument, show the current branch name. With one argument,
1016 set the working directory branch name (the branch will not exist
1016 set the working directory branch name (the branch will not exist
1017 in the repository until the next commit). Standard practice
1017 in the repository until the next commit). Standard practice
1018 recommends that primary development take place on the 'default'
1018 recommends that primary development take place on the 'default'
1019 branch.
1019 branch.
1020
1020
1021 Unless -f/--force is specified, branch will not let you set a
1021 Unless -f/--force is specified, branch will not let you set a
1022 branch name that already exists.
1022 branch name that already exists.
1023
1023
1024 Use -C/--clean to reset the working directory branch to that of
1024 Use -C/--clean to reset the working directory branch to that of
1025 the parent of the working directory, negating a previous branch
1025 the parent of the working directory, negating a previous branch
1026 change.
1026 change.
1027
1027
1028 Use the command :hg:`update` to switch to an existing branch. Use
1028 Use the command :hg:`update` to switch to an existing branch. Use
1029 :hg:`commit --close-branch` to mark this branch head as closed.
1029 :hg:`commit --close-branch` to mark this branch head as closed.
1030 When all heads of a branch are closed, the branch will be
1030 When all heads of a branch are closed, the branch will be
1031 considered closed.
1031 considered closed.
1032
1032
1033 Returns 0 on success.
1033 Returns 0 on success.
1034 """
1034 """
1035 opts = pycompat.byteskwargs(opts)
1035 opts = pycompat.byteskwargs(opts)
1036 if label:
1036 if label:
1037 label = label.strip()
1037 label = label.strip()
1038
1038
1039 if not opts.get('clean') and not label:
1039 if not opts.get('clean') and not label:
1040 ui.write("%s\n" % repo.dirstate.branch())
1040 ui.write("%s\n" % repo.dirstate.branch())
1041 return
1041 return
1042
1042
1043 with repo.wlock():
1043 with repo.wlock():
1044 if opts.get('clean'):
1044 if opts.get('clean'):
1045 label = repo[None].p1().branch()
1045 label = repo[None].p1().branch()
1046 repo.dirstate.setbranch(label)
1046 repo.dirstate.setbranch(label)
1047 ui.status(_('reset working directory to branch %s\n') % label)
1047 ui.status(_('reset working directory to branch %s\n') % label)
1048 elif label:
1048 elif label:
1049 if not opts.get('force') and label in repo.branchmap():
1049 if not opts.get('force') and label in repo.branchmap():
1050 if label not in [p.branch() for p in repo[None].parents()]:
1050 if label not in [p.branch() for p in repo[None].parents()]:
1051 raise error.Abort(_('a branch of the same name already'
1051 raise error.Abort(_('a branch of the same name already'
1052 ' exists'),
1052 ' exists'),
1053 # i18n: "it" refers to an existing branch
1053 # i18n: "it" refers to an existing branch
1054 hint=_("use 'hg update' to switch to it"))
1054 hint=_("use 'hg update' to switch to it"))
1055 scmutil.checknewlabel(repo, label, 'branch')
1055 scmutil.checknewlabel(repo, label, 'branch')
1056 repo.dirstate.setbranch(label)
1056 repo.dirstate.setbranch(label)
1057 ui.status(_('marked working directory as branch %s\n') % label)
1057 ui.status(_('marked working directory as branch %s\n') % label)
1058
1058
1059 # find any open named branches aside from default
1059 # find any open named branches aside from default
1060 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1060 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1061 if n != "default" and not c]
1061 if n != "default" and not c]
1062 if not others:
1062 if not others:
1063 ui.status(_('(branches are permanent and global, '
1063 ui.status(_('(branches are permanent and global, '
1064 'did you want a bookmark?)\n'))
1064 'did you want a bookmark?)\n'))
1065
1065
1066 @command('branches',
1066 @command('branches',
1067 [('a', 'active', False,
1067 [('a', 'active', False,
1068 _('show only branches that have unmerged heads (DEPRECATED)')),
1068 _('show only branches that have unmerged heads (DEPRECATED)')),
1069 ('c', 'closed', False, _('show normal and closed branches')),
1069 ('c', 'closed', False, _('show normal and closed branches')),
1070 ] + formatteropts,
1070 ] + formatteropts,
1071 _('[-c]'), cmdtype=readonly)
1071 _('[-c]'), cmdtype=readonly)
1072 def branches(ui, repo, active=False, closed=False, **opts):
1072 def branches(ui, repo, active=False, closed=False, **opts):
1073 """list repository named branches
1073 """list repository named branches
1074
1074
1075 List the repository's named branches, indicating which ones are
1075 List the repository's named branches, indicating which ones are
1076 inactive. If -c/--closed is specified, also list branches which have
1076 inactive. If -c/--closed is specified, also list branches which have
1077 been marked closed (see :hg:`commit --close-branch`).
1077 been marked closed (see :hg:`commit --close-branch`).
1078
1078
1079 Use the command :hg:`update` to switch to an existing branch.
1079 Use the command :hg:`update` to switch to an existing branch.
1080
1080
1081 Returns 0.
1081 Returns 0.
1082 """
1082 """
1083
1083
1084 opts = pycompat.byteskwargs(opts)
1084 opts = pycompat.byteskwargs(opts)
1085 ui.pager('branches')
1085 ui.pager('branches')
1086 fm = ui.formatter('branches', opts)
1086 fm = ui.formatter('branches', opts)
1087 hexfunc = fm.hexfunc
1087 hexfunc = fm.hexfunc
1088
1088
1089 allheads = set(repo.heads())
1089 allheads = set(repo.heads())
1090 branches = []
1090 branches = []
1091 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1091 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1092 isactive = False
1092 isactive = False
1093 if not isclosed:
1093 if not isclosed:
1094 openheads = set(repo.branchmap().iteropen(heads))
1094 openheads = set(repo.branchmap().iteropen(heads))
1095 isactive = bool(openheads & allheads)
1095 isactive = bool(openheads & allheads)
1096 branches.append((tag, repo[tip], isactive, not isclosed))
1096 branches.append((tag, repo[tip], isactive, not isclosed))
1097 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1097 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1098 reverse=True)
1098 reverse=True)
1099
1099
1100 for tag, ctx, isactive, isopen in branches:
1100 for tag, ctx, isactive, isopen in branches:
1101 if active and not isactive:
1101 if active and not isactive:
1102 continue
1102 continue
1103 if isactive:
1103 if isactive:
1104 label = 'branches.active'
1104 label = 'branches.active'
1105 notice = ''
1105 notice = ''
1106 elif not isopen:
1106 elif not isopen:
1107 if not closed:
1107 if not closed:
1108 continue
1108 continue
1109 label = 'branches.closed'
1109 label = 'branches.closed'
1110 notice = _(' (closed)')
1110 notice = _(' (closed)')
1111 else:
1111 else:
1112 label = 'branches.inactive'
1112 label = 'branches.inactive'
1113 notice = _(' (inactive)')
1113 notice = _(' (inactive)')
1114 current = (tag == repo.dirstate.branch())
1114 current = (tag == repo.dirstate.branch())
1115 if current:
1115 if current:
1116 label = 'branches.current'
1116 label = 'branches.current'
1117
1117
1118 fm.startitem()
1118 fm.startitem()
1119 fm.write('branch', '%s', tag, label=label)
1119 fm.write('branch', '%s', tag, label=label)
1120 rev = ctx.rev()
1120 rev = ctx.rev()
1121 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1121 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1122 fmt = ' ' * padsize + ' %d:%s'
1122 fmt = ' ' * padsize + ' %d:%s'
1123 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1123 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1124 label='log.changeset changeset.%s' % ctx.phasestr())
1124 label='log.changeset changeset.%s' % ctx.phasestr())
1125 fm.context(ctx=ctx)
1125 fm.context(ctx=ctx)
1126 fm.data(active=isactive, closed=not isopen, current=current)
1126 fm.data(active=isactive, closed=not isopen, current=current)
1127 if not ui.quiet:
1127 if not ui.quiet:
1128 fm.plain(notice)
1128 fm.plain(notice)
1129 fm.plain('\n')
1129 fm.plain('\n')
1130 fm.end()
1130 fm.end()
1131
1131
1132 @command('bundle',
1132 @command('bundle',
1133 [('f', 'force', None, _('run even when the destination is unrelated')),
1133 [('f', 'force', None, _('run even when the destination is unrelated')),
1134 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1134 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1135 _('REV')),
1135 _('REV')),
1136 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1136 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1137 _('BRANCH')),
1137 _('BRANCH')),
1138 ('', 'base', [],
1138 ('', 'base', [],
1139 _('a base changeset assumed to be available at the destination'),
1139 _('a base changeset assumed to be available at the destination'),
1140 _('REV')),
1140 _('REV')),
1141 ('a', 'all', None, _('bundle all changesets in the repository')),
1141 ('a', 'all', None, _('bundle all changesets in the repository')),
1142 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1142 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1143 ] + remoteopts,
1143 ] + remoteopts,
1144 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1144 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1145 def bundle(ui, repo, fname, dest=None, **opts):
1145 def bundle(ui, repo, fname, dest=None, **opts):
1146 """create a bundle file
1146 """create a bundle file
1147
1147
1148 Generate a bundle file containing data to be added to a repository.
1148 Generate a bundle file containing data to be added to a repository.
1149
1149
1150 To create a bundle containing all changesets, use -a/--all
1150 To create a bundle containing all changesets, use -a/--all
1151 (or --base null). Otherwise, hg assumes the destination will have
1151 (or --base null). Otherwise, hg assumes the destination will have
1152 all the nodes you specify with --base parameters. Otherwise, hg
1152 all the nodes you specify with --base parameters. Otherwise, hg
1153 will assume the repository has all the nodes in destination, or
1153 will assume the repository has all the nodes in destination, or
1154 default-push/default if no destination is specified.
1154 default-push/default if no destination is specified.
1155
1155
1156 You can change bundle format with the -t/--type option. See
1156 You can change bundle format with the -t/--type option. See
1157 :hg:`help bundlespec` for documentation on this format. By default,
1157 :hg:`help bundlespec` for documentation on this format. By default,
1158 the most appropriate format is used and compression defaults to
1158 the most appropriate format is used and compression defaults to
1159 bzip2.
1159 bzip2.
1160
1160
1161 The bundle file can then be transferred using conventional means
1161 The bundle file can then be transferred using conventional means
1162 and applied to another repository with the unbundle or pull
1162 and applied to another repository with the unbundle or pull
1163 command. This is useful when direct push and pull are not
1163 command. This is useful when direct push and pull are not
1164 available or when exporting an entire repository is undesirable.
1164 available or when exporting an entire repository is undesirable.
1165
1165
1166 Applying bundles preserves all changeset contents including
1166 Applying bundles preserves all changeset contents including
1167 permissions, copy/rename information, and revision history.
1167 permissions, copy/rename information, and revision history.
1168
1168
1169 Returns 0 on success, 1 if no changes found.
1169 Returns 0 on success, 1 if no changes found.
1170 """
1170 """
1171 opts = pycompat.byteskwargs(opts)
1171 opts = pycompat.byteskwargs(opts)
1172 revs = None
1172 revs = None
1173 if 'rev' in opts:
1173 if 'rev' in opts:
1174 revstrings = opts['rev']
1174 revstrings = opts['rev']
1175 revs = scmutil.revrange(repo, revstrings)
1175 revs = scmutil.revrange(repo, revstrings)
1176 if revstrings and not revs:
1176 if revstrings and not revs:
1177 raise error.Abort(_('no commits to bundle'))
1177 raise error.Abort(_('no commits to bundle'))
1178
1178
1179 bundletype = opts.get('type', 'bzip2').lower()
1179 bundletype = opts.get('type', 'bzip2').lower()
1180 try:
1180 try:
1181 bcompression, cgversion, params = exchange.parsebundlespec(
1181 bcompression, cgversion, params = exchange.parsebundlespec(
1182 repo, bundletype, strict=False)
1182 repo, bundletype, strict=False)
1183 except error.UnsupportedBundleSpecification as e:
1183 except error.UnsupportedBundleSpecification as e:
1184 raise error.Abort(str(e),
1184 raise error.Abort(str(e),
1185 hint=_("see 'hg help bundlespec' for supported "
1185 hint=_("see 'hg help bundlespec' for supported "
1186 "values for --type"))
1186 "values for --type"))
1187
1187
1188 # Packed bundles are a pseudo bundle format for now.
1188 # Packed bundles are a pseudo bundle format for now.
1189 if cgversion == 's1':
1189 if cgversion == 's1':
1190 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1190 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1191 hint=_("use 'hg debugcreatestreamclonebundle'"))
1191 hint=_("use 'hg debugcreatestreamclonebundle'"))
1192
1192
1193 if opts.get('all'):
1193 if opts.get('all'):
1194 if dest:
1194 if dest:
1195 raise error.Abort(_("--all is incompatible with specifying "
1195 raise error.Abort(_("--all is incompatible with specifying "
1196 "a destination"))
1196 "a destination"))
1197 if opts.get('base'):
1197 if opts.get('base'):
1198 ui.warn(_("ignoring --base because --all was specified\n"))
1198 ui.warn(_("ignoring --base because --all was specified\n"))
1199 base = ['null']
1199 base = ['null']
1200 else:
1200 else:
1201 base = scmutil.revrange(repo, opts.get('base'))
1201 base = scmutil.revrange(repo, opts.get('base'))
1202 if cgversion not in changegroup.supportedoutgoingversions(repo):
1202 if cgversion not in changegroup.supportedoutgoingversions(repo):
1203 raise error.Abort(_("repository does not support bundle version %s") %
1203 raise error.Abort(_("repository does not support bundle version %s") %
1204 cgversion)
1204 cgversion)
1205
1205
1206 if base:
1206 if base:
1207 if dest:
1207 if dest:
1208 raise error.Abort(_("--base is incompatible with specifying "
1208 raise error.Abort(_("--base is incompatible with specifying "
1209 "a destination"))
1209 "a destination"))
1210 common = [repo.lookup(rev) for rev in base]
1210 common = [repo.lookup(rev) for rev in base]
1211 heads = revs and map(repo.lookup, revs) or None
1211 heads = revs and map(repo.lookup, revs) or None
1212 outgoing = discovery.outgoing(repo, common, heads)
1212 outgoing = discovery.outgoing(repo, common, heads)
1213 else:
1213 else:
1214 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1214 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1215 dest, branches = hg.parseurl(dest, opts.get('branch'))
1215 dest, branches = hg.parseurl(dest, opts.get('branch'))
1216 other = hg.peer(repo, opts, dest)
1216 other = hg.peer(repo, opts, dest)
1217 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1217 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1218 heads = revs and map(repo.lookup, revs) or revs
1218 heads = revs and map(repo.lookup, revs) or revs
1219 outgoing = discovery.findcommonoutgoing(repo, other,
1219 outgoing = discovery.findcommonoutgoing(repo, other,
1220 onlyheads=heads,
1220 onlyheads=heads,
1221 force=opts.get('force'),
1221 force=opts.get('force'),
1222 portable=True)
1222 portable=True)
1223
1223
1224 if not outgoing.missing:
1224 if not outgoing.missing:
1225 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1225 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1226 return 1
1226 return 1
1227
1227
1228 if cgversion == '01': #bundle1
1228 if cgversion == '01': #bundle1
1229 if bcompression is None:
1229 if bcompression is None:
1230 bcompression = 'UN'
1230 bcompression = 'UN'
1231 bversion = 'HG10' + bcompression
1231 bversion = 'HG10' + bcompression
1232 bcompression = None
1232 bcompression = None
1233 elif cgversion in ('02', '03'):
1233 elif cgversion in ('02', '03'):
1234 bversion = 'HG20'
1234 bversion = 'HG20'
1235 else:
1235 else:
1236 raise error.ProgrammingError(
1236 raise error.ProgrammingError(
1237 'bundle: unexpected changegroup version %s' % cgversion)
1237 'bundle: unexpected changegroup version %s' % cgversion)
1238
1238
1239 # TODO compression options should be derived from bundlespec parsing.
1239 # TODO compression options should be derived from bundlespec parsing.
1240 # This is a temporary hack to allow adjusting bundle compression
1240 # This is a temporary hack to allow adjusting bundle compression
1241 # level without a) formalizing the bundlespec changes to declare it
1241 # level without a) formalizing the bundlespec changes to declare it
1242 # b) introducing a command flag.
1242 # b) introducing a command flag.
1243 compopts = {}
1243 compopts = {}
1244 complevel = ui.configint('experimental', 'bundlecomplevel')
1244 complevel = ui.configint('experimental', 'bundlecomplevel')
1245 if complevel is not None:
1245 if complevel is not None:
1246 compopts['level'] = complevel
1246 compopts['level'] = complevel
1247
1247
1248
1248
1249 contentopts = {'cg.version': cgversion}
1249 contentopts = {'cg.version': cgversion}
1250 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1250 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1251 contentopts['obsolescence'] = True
1251 contentopts['obsolescence'] = True
1252 if repo.ui.configbool('experimental', 'bundle-phases'):
1252 if repo.ui.configbool('experimental', 'bundle-phases'):
1253 contentopts['phases'] = True
1253 contentopts['phases'] = True
1254 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1254 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1255 contentopts, compression=bcompression,
1255 contentopts, compression=bcompression,
1256 compopts=compopts)
1256 compopts=compopts)
1257
1257
1258 @command('cat',
1258 @command('cat',
1259 [('o', 'output', '',
1259 [('o', 'output', '',
1260 _('print output to file with formatted name'), _('FORMAT')),
1260 _('print output to file with formatted name'), _('FORMAT')),
1261 ('r', 'rev', '', _('print the given revision'), _('REV')),
1261 ('r', 'rev', '', _('print the given revision'), _('REV')),
1262 ('', 'decode', None, _('apply any matching decode filter')),
1262 ('', 'decode', None, _('apply any matching decode filter')),
1263 ] + walkopts + formatteropts,
1263 ] + walkopts + formatteropts,
1264 _('[OPTION]... FILE...'),
1264 _('[OPTION]... FILE...'),
1265 inferrepo=True, cmdtype=readonly)
1265 inferrepo=True, cmdtype=readonly)
1266 def cat(ui, repo, file1, *pats, **opts):
1266 def cat(ui, repo, file1, *pats, **opts):
1267 """output the current or given revision of files
1267 """output the current or given revision of files
1268
1268
1269 Print the specified files as they were at the given revision. If
1269 Print the specified files as they were at the given revision. If
1270 no revision is given, the parent of the working directory is used.
1270 no revision is given, the parent of the working directory is used.
1271
1271
1272 Output may be to a file, in which case the name of the file is
1272 Output may be to a file, in which case the name of the file is
1273 given using a format string. The formatting rules as follows:
1273 given using a format string. The formatting rules as follows:
1274
1274
1275 :``%%``: literal "%" character
1275 :``%%``: literal "%" character
1276 :``%s``: basename of file being printed
1276 :``%s``: basename of file being printed
1277 :``%d``: dirname of file being printed, or '.' if in repository root
1277 :``%d``: dirname of file being printed, or '.' if in repository root
1278 :``%p``: root-relative path name of file being printed
1278 :``%p``: root-relative path name of file being printed
1279 :``%H``: changeset hash (40 hexadecimal digits)
1279 :``%H``: changeset hash (40 hexadecimal digits)
1280 :``%R``: changeset revision number
1280 :``%R``: changeset revision number
1281 :``%h``: short-form changeset hash (12 hexadecimal digits)
1281 :``%h``: short-form changeset hash (12 hexadecimal digits)
1282 :``%r``: zero-padded changeset revision number
1282 :``%r``: zero-padded changeset revision number
1283 :``%b``: basename of the exporting repository
1283 :``%b``: basename of the exporting repository
1284
1284
1285 Returns 0 on success.
1285 Returns 0 on success.
1286 """
1286 """
1287 opts = pycompat.byteskwargs(opts)
1287 opts = pycompat.byteskwargs(opts)
1288 rev = opts.get('rev')
1288 rev = opts.get('rev')
1289 if rev:
1289 if rev:
1290 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1290 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1291 ctx = scmutil.revsingle(repo, rev)
1291 ctx = scmutil.revsingle(repo, rev)
1292 m = scmutil.match(ctx, (file1,) + pats, opts)
1292 m = scmutil.match(ctx, (file1,) + pats, opts)
1293 fntemplate = opts.pop('output', '')
1293 fntemplate = opts.pop('output', '')
1294 if cmdutil.isstdiofilename(fntemplate):
1294 if cmdutil.isstdiofilename(fntemplate):
1295 fntemplate = ''
1295 fntemplate = ''
1296
1296
1297 if fntemplate:
1297 if fntemplate:
1298 fm = formatter.nullformatter(ui, 'cat')
1298 fm = formatter.nullformatter(ui, 'cat')
1299 else:
1299 else:
1300 ui.pager('cat')
1300 ui.pager('cat')
1301 fm = ui.formatter('cat', opts)
1301 fm = ui.formatter('cat', opts)
1302 with fm:
1302 with fm:
1303 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1303 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1304 **pycompat.strkwargs(opts))
1304 **pycompat.strkwargs(opts))
1305
1305
1306 @command('^clone',
1306 @command('^clone',
1307 [('U', 'noupdate', None, _('the clone will include an empty working '
1307 [('U', 'noupdate', None, _('the clone will include an empty working '
1308 'directory (only a repository)')),
1308 'directory (only a repository)')),
1309 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1309 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1310 _('REV')),
1310 _('REV')),
1311 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1311 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1312 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1312 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1313 ('', 'pull', None, _('use pull protocol to copy metadata')),
1313 ('', 'pull', None, _('use pull protocol to copy metadata')),
1314 ('', 'uncompressed', None,
1314 ('', 'uncompressed', None,
1315 _('an alias to --stream (DEPRECATED)')),
1315 _('an alias to --stream (DEPRECATED)')),
1316 ('', 'stream', None,
1316 ('', 'stream', None,
1317 _('clone with minimal data processing')),
1317 _('clone with minimal data processing')),
1318 ] + remoteopts,
1318 ] + remoteopts,
1319 _('[OPTION]... SOURCE [DEST]'),
1319 _('[OPTION]... SOURCE [DEST]'),
1320 norepo=True)
1320 norepo=True)
1321 def clone(ui, source, dest=None, **opts):
1321 def clone(ui, source, dest=None, **opts):
1322 """make a copy of an existing repository
1322 """make a copy of an existing repository
1323
1323
1324 Create a copy of an existing repository in a new directory.
1324 Create a copy of an existing repository in a new directory.
1325
1325
1326 If no destination directory name is specified, it defaults to the
1326 If no destination directory name is specified, it defaults to the
1327 basename of the source.
1327 basename of the source.
1328
1328
1329 The location of the source is added to the new repository's
1329 The location of the source is added to the new repository's
1330 ``.hg/hgrc`` file, as the default to be used for future pulls.
1330 ``.hg/hgrc`` file, as the default to be used for future pulls.
1331
1331
1332 Only local paths and ``ssh://`` URLs are supported as
1332 Only local paths and ``ssh://`` URLs are supported as
1333 destinations. For ``ssh://`` destinations, no working directory or
1333 destinations. For ``ssh://`` destinations, no working directory or
1334 ``.hg/hgrc`` will be created on the remote side.
1334 ``.hg/hgrc`` will be created on the remote side.
1335
1335
1336 If the source repository has a bookmark called '@' set, that
1336 If the source repository has a bookmark called '@' set, that
1337 revision will be checked out in the new repository by default.
1337 revision will be checked out in the new repository by default.
1338
1338
1339 To check out a particular version, use -u/--update, or
1339 To check out a particular version, use -u/--update, or
1340 -U/--noupdate to create a clone with no working directory.
1340 -U/--noupdate to create a clone with no working directory.
1341
1341
1342 To pull only a subset of changesets, specify one or more revisions
1342 To pull only a subset of changesets, specify one or more revisions
1343 identifiers with -r/--rev or branches with -b/--branch. The
1343 identifiers with -r/--rev or branches with -b/--branch. The
1344 resulting clone will contain only the specified changesets and
1344 resulting clone will contain only the specified changesets and
1345 their ancestors. These options (or 'clone src#rev dest') imply
1345 their ancestors. These options (or 'clone src#rev dest') imply
1346 --pull, even for local source repositories.
1346 --pull, even for local source repositories.
1347
1347
1348 In normal clone mode, the remote normalizes repository data into a common
1348 In normal clone mode, the remote normalizes repository data into a common
1349 exchange format and the receiving end translates this data into its local
1349 exchange format and the receiving end translates this data into its local
1350 storage format. --stream activates a different clone mode that essentially
1350 storage format. --stream activates a different clone mode that essentially
1351 copies repository files from the remote with minimal data processing. This
1351 copies repository files from the remote with minimal data processing. This
1352 significantly reduces the CPU cost of a clone both remotely and locally.
1352 significantly reduces the CPU cost of a clone both remotely and locally.
1353 However, it often increases the transferred data size by 30-40%. This can
1353 However, it often increases the transferred data size by 30-40%. This can
1354 result in substantially faster clones where I/O throughput is plentiful,
1354 result in substantially faster clones where I/O throughput is plentiful,
1355 especially for larger repositories. A side-effect of --stream clones is
1355 especially for larger repositories. A side-effect of --stream clones is
1356 that storage settings and requirements on the remote are applied locally:
1356 that storage settings and requirements on the remote are applied locally:
1357 a modern client may inherit legacy or inefficient storage used by the
1357 a modern client may inherit legacy or inefficient storage used by the
1358 remote or a legacy Mercurial client may not be able to clone from a
1358 remote or a legacy Mercurial client may not be able to clone from a
1359 modern Mercurial remote.
1359 modern Mercurial remote.
1360
1360
1361 .. note::
1361 .. note::
1362
1362
1363 Specifying a tag will include the tagged changeset but not the
1363 Specifying a tag will include the tagged changeset but not the
1364 changeset containing the tag.
1364 changeset containing the tag.
1365
1365
1366 .. container:: verbose
1366 .. container:: verbose
1367
1367
1368 For efficiency, hardlinks are used for cloning whenever the
1368 For efficiency, hardlinks are used for cloning whenever the
1369 source and destination are on the same filesystem (note this
1369 source and destination are on the same filesystem (note this
1370 applies only to the repository data, not to the working
1370 applies only to the repository data, not to the working
1371 directory). Some filesystems, such as AFS, implement hardlinking
1371 directory). Some filesystems, such as AFS, implement hardlinking
1372 incorrectly, but do not report errors. In these cases, use the
1372 incorrectly, but do not report errors. In these cases, use the
1373 --pull option to avoid hardlinking.
1373 --pull option to avoid hardlinking.
1374
1374
1375 Mercurial will update the working directory to the first applicable
1375 Mercurial will update the working directory to the first applicable
1376 revision from this list:
1376 revision from this list:
1377
1377
1378 a) null if -U or the source repository has no changesets
1378 a) null if -U or the source repository has no changesets
1379 b) if -u . and the source repository is local, the first parent of
1379 b) if -u . and the source repository is local, the first parent of
1380 the source repository's working directory
1380 the source repository's working directory
1381 c) the changeset specified with -u (if a branch name, this means the
1381 c) the changeset specified with -u (if a branch name, this means the
1382 latest head of that branch)
1382 latest head of that branch)
1383 d) the changeset specified with -r
1383 d) the changeset specified with -r
1384 e) the tipmost head specified with -b
1384 e) the tipmost head specified with -b
1385 f) the tipmost head specified with the url#branch source syntax
1385 f) the tipmost head specified with the url#branch source syntax
1386 g) the revision marked with the '@' bookmark, if present
1386 g) the revision marked with the '@' bookmark, if present
1387 h) the tipmost head of the default branch
1387 h) the tipmost head of the default branch
1388 i) tip
1388 i) tip
1389
1389
1390 When cloning from servers that support it, Mercurial may fetch
1390 When cloning from servers that support it, Mercurial may fetch
1391 pre-generated data from a server-advertised URL. When this is done,
1391 pre-generated data from a server-advertised URL. When this is done,
1392 hooks operating on incoming changesets and changegroups may fire twice,
1392 hooks operating on incoming changesets and changegroups may fire twice,
1393 once for the bundle fetched from the URL and another for any additional
1393 once for the bundle fetched from the URL and another for any additional
1394 data not fetched from this URL. In addition, if an error occurs, the
1394 data not fetched from this URL. In addition, if an error occurs, the
1395 repository may be rolled back to a partial clone. This behavior may
1395 repository may be rolled back to a partial clone. This behavior may
1396 change in future releases. See :hg:`help -e clonebundles` for more.
1396 change in future releases. See :hg:`help -e clonebundles` for more.
1397
1397
1398 Examples:
1398 Examples:
1399
1399
1400 - clone a remote repository to a new directory named hg/::
1400 - clone a remote repository to a new directory named hg/::
1401
1401
1402 hg clone https://www.mercurial-scm.org/repo/hg/
1402 hg clone https://www.mercurial-scm.org/repo/hg/
1403
1403
1404 - create a lightweight local clone::
1404 - create a lightweight local clone::
1405
1405
1406 hg clone project/ project-feature/
1406 hg clone project/ project-feature/
1407
1407
1408 - clone from an absolute path on an ssh server (note double-slash)::
1408 - clone from an absolute path on an ssh server (note double-slash)::
1409
1409
1410 hg clone ssh://user@server//home/projects/alpha/
1410 hg clone ssh://user@server//home/projects/alpha/
1411
1411
1412 - do a streaming clone while checking out a specified version::
1412 - do a streaming clone while checking out a specified version::
1413
1413
1414 hg clone --stream http://server/repo -u 1.5
1414 hg clone --stream http://server/repo -u 1.5
1415
1415
1416 - create a repository without changesets after a particular revision::
1416 - create a repository without changesets after a particular revision::
1417
1417
1418 hg clone -r 04e544 experimental/ good/
1418 hg clone -r 04e544 experimental/ good/
1419
1419
1420 - clone (and track) a particular named branch::
1420 - clone (and track) a particular named branch::
1421
1421
1422 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1422 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1423
1423
1424 See :hg:`help urls` for details on specifying URLs.
1424 See :hg:`help urls` for details on specifying URLs.
1425
1425
1426 Returns 0 on success.
1426 Returns 0 on success.
1427 """
1427 """
1428 opts = pycompat.byteskwargs(opts)
1428 opts = pycompat.byteskwargs(opts)
1429 if opts.get('noupdate') and opts.get('updaterev'):
1429 if opts.get('noupdate') and opts.get('updaterev'):
1430 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1430 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1431
1431
1432 r = hg.clone(ui, opts, source, dest,
1432 r = hg.clone(ui, opts, source, dest,
1433 pull=opts.get('pull'),
1433 pull=opts.get('pull'),
1434 stream=opts.get('stream') or opts.get('uncompressed'),
1434 stream=opts.get('stream') or opts.get('uncompressed'),
1435 rev=opts.get('rev'),
1435 rev=opts.get('rev'),
1436 update=opts.get('updaterev') or not opts.get('noupdate'),
1436 update=opts.get('updaterev') or not opts.get('noupdate'),
1437 branch=opts.get('branch'),
1437 branch=opts.get('branch'),
1438 shareopts=opts.get('shareopts'))
1438 shareopts=opts.get('shareopts'))
1439
1439
1440 return r is None
1440 return r is None
1441
1441
1442 @command('^commit|ci',
1442 @command('^commit|ci',
1443 [('A', 'addremove', None,
1443 [('A', 'addremove', None,
1444 _('mark new/missing files as added/removed before committing')),
1444 _('mark new/missing files as added/removed before committing')),
1445 ('', 'close-branch', None,
1445 ('', 'close-branch', None,
1446 _('mark a branch head as closed')),
1446 _('mark a branch head as closed')),
1447 ('', 'amend', None, _('amend the parent of the working directory')),
1447 ('', 'amend', None, _('amend the parent of the working directory')),
1448 ('s', 'secret', None, _('use the secret phase for committing')),
1448 ('s', 'secret', None, _('use the secret phase for committing')),
1449 ('e', 'edit', None, _('invoke editor on commit messages')),
1449 ('e', 'edit', None, _('invoke editor on commit messages')),
1450 ('i', 'interactive', None, _('use interactive mode')),
1450 ('i', 'interactive', None, _('use interactive mode')),
1451 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1451 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1452 _('[OPTION]... [FILE]...'),
1452 _('[OPTION]... [FILE]...'),
1453 inferrepo=True)
1453 inferrepo=True)
1454 def commit(ui, repo, *pats, **opts):
1454 def commit(ui, repo, *pats, **opts):
1455 """commit the specified files or all outstanding changes
1455 """commit the specified files or all outstanding changes
1456
1456
1457 Commit changes to the given files into the repository. Unlike a
1457 Commit changes to the given files into the repository. Unlike a
1458 centralized SCM, this operation is a local operation. See
1458 centralized SCM, this operation is a local operation. See
1459 :hg:`push` for a way to actively distribute your changes.
1459 :hg:`push` for a way to actively distribute your changes.
1460
1460
1461 If a list of files is omitted, all changes reported by :hg:`status`
1461 If a list of files is omitted, all changes reported by :hg:`status`
1462 will be committed.
1462 will be committed.
1463
1463
1464 If you are committing the result of a merge, do not provide any
1464 If you are committing the result of a merge, do not provide any
1465 filenames or -I/-X filters.
1465 filenames or -I/-X filters.
1466
1466
1467 If no commit message is specified, Mercurial starts your
1467 If no commit message is specified, Mercurial starts your
1468 configured editor where you can enter a message. In case your
1468 configured editor where you can enter a message. In case your
1469 commit fails, you will find a backup of your message in
1469 commit fails, you will find a backup of your message in
1470 ``.hg/last-message.txt``.
1470 ``.hg/last-message.txt``.
1471
1471
1472 The --close-branch flag can be used to mark the current branch
1472 The --close-branch flag can be used to mark the current branch
1473 head closed. When all heads of a branch are closed, the branch
1473 head closed. When all heads of a branch are closed, the branch
1474 will be considered closed and no longer listed.
1474 will be considered closed and no longer listed.
1475
1475
1476 The --amend flag can be used to amend the parent of the
1476 The --amend flag can be used to amend the parent of the
1477 working directory with a new commit that contains the changes
1477 working directory with a new commit that contains the changes
1478 in the parent in addition to those currently reported by :hg:`status`,
1478 in the parent in addition to those currently reported by :hg:`status`,
1479 if there are any. The old commit is stored in a backup bundle in
1479 if there are any. The old commit is stored in a backup bundle in
1480 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1480 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1481 on how to restore it).
1481 on how to restore it).
1482
1482
1483 Message, user and date are taken from the amended commit unless
1483 Message, user and date are taken from the amended commit unless
1484 specified. When a message isn't specified on the command line,
1484 specified. When a message isn't specified on the command line,
1485 the editor will open with the message of the amended commit.
1485 the editor will open with the message of the amended commit.
1486
1486
1487 It is not possible to amend public changesets (see :hg:`help phases`)
1487 It is not possible to amend public changesets (see :hg:`help phases`)
1488 or changesets that have children.
1488 or changesets that have children.
1489
1489
1490 See :hg:`help dates` for a list of formats valid for -d/--date.
1490 See :hg:`help dates` for a list of formats valid for -d/--date.
1491
1491
1492 Returns 0 on success, 1 if nothing changed.
1492 Returns 0 on success, 1 if nothing changed.
1493
1493
1494 .. container:: verbose
1494 .. container:: verbose
1495
1495
1496 Examples:
1496 Examples:
1497
1497
1498 - commit all files ending in .py::
1498 - commit all files ending in .py::
1499
1499
1500 hg commit --include "set:**.py"
1500 hg commit --include "set:**.py"
1501
1501
1502 - commit all non-binary files::
1502 - commit all non-binary files::
1503
1503
1504 hg commit --exclude "set:binary()"
1504 hg commit --exclude "set:binary()"
1505
1505
1506 - amend the current commit and set the date to now::
1506 - amend the current commit and set the date to now::
1507
1507
1508 hg commit --amend --date now
1508 hg commit --amend --date now
1509 """
1509 """
1510 wlock = lock = None
1510 wlock = lock = None
1511 try:
1511 try:
1512 wlock = repo.wlock()
1512 wlock = repo.wlock()
1513 lock = repo.lock()
1513 lock = repo.lock()
1514 return _docommit(ui, repo, *pats, **opts)
1514 return _docommit(ui, repo, *pats, **opts)
1515 finally:
1515 finally:
1516 release(lock, wlock)
1516 release(lock, wlock)
1517
1517
1518 def _docommit(ui, repo, *pats, **opts):
1518 def _docommit(ui, repo, *pats, **opts):
1519 if opts.get(r'interactive'):
1519 if opts.get(r'interactive'):
1520 opts.pop(r'interactive')
1520 opts.pop(r'interactive')
1521 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1521 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1522 cmdutil.recordfilter, *pats,
1522 cmdutil.recordfilter, *pats,
1523 **opts)
1523 **opts)
1524 # ret can be 0 (no changes to record) or the value returned by
1524 # ret can be 0 (no changes to record) or the value returned by
1525 # commit(), 1 if nothing changed or None on success.
1525 # commit(), 1 if nothing changed or None on success.
1526 return 1 if ret == 0 else ret
1526 return 1 if ret == 0 else ret
1527
1527
1528 opts = pycompat.byteskwargs(opts)
1528 opts = pycompat.byteskwargs(opts)
1529 if opts.get('subrepos'):
1529 if opts.get('subrepos'):
1530 if opts.get('amend'):
1530 if opts.get('amend'):
1531 raise error.Abort(_('cannot amend with --subrepos'))
1531 raise error.Abort(_('cannot amend with --subrepos'))
1532 # Let --subrepos on the command line override config setting.
1532 # Let --subrepos on the command line override config setting.
1533 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1533 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1534
1534
1535 cmdutil.checkunfinished(repo, commit=True)
1535 cmdutil.checkunfinished(repo, commit=True)
1536
1536
1537 branch = repo[None].branch()
1537 branch = repo[None].branch()
1538 bheads = repo.branchheads(branch)
1538 bheads = repo.branchheads(branch)
1539
1539
1540 extra = {}
1540 extra = {}
1541 if opts.get('close_branch'):
1541 if opts.get('close_branch'):
1542 extra['close'] = 1
1542 extra['close'] = 1
1543
1543
1544 if not bheads:
1544 if not bheads:
1545 raise error.Abort(_('can only close branch heads'))
1545 raise error.Abort(_('can only close branch heads'))
1546 elif opts.get('amend'):
1546 elif opts.get('amend'):
1547 if repo[None].parents()[0].p1().branch() != branch and \
1547 if repo[None].parents()[0].p1().branch() != branch and \
1548 repo[None].parents()[0].p2().branch() != branch:
1548 repo[None].parents()[0].p2().branch() != branch:
1549 raise error.Abort(_('can only close branch heads'))
1549 raise error.Abort(_('can only close branch heads'))
1550
1550
1551 if opts.get('amend'):
1551 if opts.get('amend'):
1552 if ui.configbool('ui', 'commitsubrepos'):
1552 if ui.configbool('ui', 'commitsubrepos'):
1553 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1553 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1554
1554
1555 old = repo['.']
1555 old = repo['.']
1556 rewriteutil.precheck(repo, [old.rev()], 'amend')
1556 rewriteutil.precheck(repo, [old.rev()], 'amend')
1557
1557
1558 # Currently histedit gets confused if an amend happens while histedit
1558 # Currently histedit gets confused if an amend happens while histedit
1559 # is in progress. Since we have a checkunfinished command, we are
1559 # is in progress. Since we have a checkunfinished command, we are
1560 # temporarily honoring it.
1560 # temporarily honoring it.
1561 #
1561 #
1562 # Note: eventually this guard will be removed. Please do not expect
1562 # Note: eventually this guard will be removed. Please do not expect
1563 # this behavior to remain.
1563 # this behavior to remain.
1564 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1564 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1565 cmdutil.checkunfinished(repo)
1565 cmdutil.checkunfinished(repo)
1566
1566
1567 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1567 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1568 if node == old.node():
1568 if node == old.node():
1569 ui.status(_("nothing changed\n"))
1569 ui.status(_("nothing changed\n"))
1570 return 1
1570 return 1
1571 else:
1571 else:
1572 def commitfunc(ui, repo, message, match, opts):
1572 def commitfunc(ui, repo, message, match, opts):
1573 overrides = {}
1573 overrides = {}
1574 if opts.get('secret'):
1574 if opts.get('secret'):
1575 overrides[('phases', 'new-commit')] = 'secret'
1575 overrides[('phases', 'new-commit')] = 'secret'
1576
1576
1577 baseui = repo.baseui
1577 baseui = repo.baseui
1578 with baseui.configoverride(overrides, 'commit'):
1578 with baseui.configoverride(overrides, 'commit'):
1579 with ui.configoverride(overrides, 'commit'):
1579 with ui.configoverride(overrides, 'commit'):
1580 editform = cmdutil.mergeeditform(repo[None],
1580 editform = cmdutil.mergeeditform(repo[None],
1581 'commit.normal')
1581 'commit.normal')
1582 editor = cmdutil.getcommiteditor(
1582 editor = cmdutil.getcommiteditor(
1583 editform=editform, **pycompat.strkwargs(opts))
1583 editform=editform, **pycompat.strkwargs(opts))
1584 return repo.commit(message,
1584 return repo.commit(message,
1585 opts.get('user'),
1585 opts.get('user'),
1586 opts.get('date'),
1586 opts.get('date'),
1587 match,
1587 match,
1588 editor=editor,
1588 editor=editor,
1589 extra=extra)
1589 extra=extra)
1590
1590
1591 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1591 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1592
1592
1593 if not node:
1593 if not node:
1594 stat = cmdutil.postcommitstatus(repo, pats, opts)
1594 stat = cmdutil.postcommitstatus(repo, pats, opts)
1595 if stat[3]:
1595 if stat[3]:
1596 ui.status(_("nothing changed (%d missing files, see "
1596 ui.status(_("nothing changed (%d missing files, see "
1597 "'hg status')\n") % len(stat[3]))
1597 "'hg status')\n") % len(stat[3]))
1598 else:
1598 else:
1599 ui.status(_("nothing changed\n"))
1599 ui.status(_("nothing changed\n"))
1600 return 1
1600 return 1
1601
1601
1602 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1602 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1603
1603
1604 @command('config|showconfig|debugconfig',
1604 @command('config|showconfig|debugconfig',
1605 [('u', 'untrusted', None, _('show untrusted configuration options')),
1605 [('u', 'untrusted', None, _('show untrusted configuration options')),
1606 ('e', 'edit', None, _('edit user config')),
1606 ('e', 'edit', None, _('edit user config')),
1607 ('l', 'local', None, _('edit repository config')),
1607 ('l', 'local', None, _('edit repository config')),
1608 ('g', 'global', None, _('edit global config'))] + formatteropts,
1608 ('g', 'global', None, _('edit global config'))] + formatteropts,
1609 _('[-u] [NAME]...'),
1609 _('[-u] [NAME]...'),
1610 optionalrepo=True, cmdtype=readonly)
1610 optionalrepo=True, cmdtype=readonly)
1611 def config(ui, repo, *values, **opts):
1611 def config(ui, repo, *values, **opts):
1612 """show combined config settings from all hgrc files
1612 """show combined config settings from all hgrc files
1613
1613
1614 With no arguments, print names and values of all config items.
1614 With no arguments, print names and values of all config items.
1615
1615
1616 With one argument of the form section.name, print just the value
1616 With one argument of the form section.name, print just the value
1617 of that config item.
1617 of that config item.
1618
1618
1619 With multiple arguments, print names and values of all config
1619 With multiple arguments, print names and values of all config
1620 items with matching section names.
1620 items with matching section names.
1621
1621
1622 With --edit, start an editor on the user-level config file. With
1622 With --edit, start an editor on the user-level config file. With
1623 --global, edit the system-wide config file. With --local, edit the
1623 --global, edit the system-wide config file. With --local, edit the
1624 repository-level config file.
1624 repository-level config file.
1625
1625
1626 With --debug, the source (filename and line number) is printed
1626 With --debug, the source (filename and line number) is printed
1627 for each config item.
1627 for each config item.
1628
1628
1629 See :hg:`help config` for more information about config files.
1629 See :hg:`help config` for more information about config files.
1630
1630
1631 Returns 0 on success, 1 if NAME does not exist.
1631 Returns 0 on success, 1 if NAME does not exist.
1632
1632
1633 """
1633 """
1634
1634
1635 opts = pycompat.byteskwargs(opts)
1635 opts = pycompat.byteskwargs(opts)
1636 if opts.get('edit') or opts.get('local') or opts.get('global'):
1636 if opts.get('edit') or opts.get('local') or opts.get('global'):
1637 if opts.get('local') and opts.get('global'):
1637 if opts.get('local') and opts.get('global'):
1638 raise error.Abort(_("can't use --local and --global together"))
1638 raise error.Abort(_("can't use --local and --global together"))
1639
1639
1640 if opts.get('local'):
1640 if opts.get('local'):
1641 if not repo:
1641 if not repo:
1642 raise error.Abort(_("can't use --local outside a repository"))
1642 raise error.Abort(_("can't use --local outside a repository"))
1643 paths = [repo.vfs.join('hgrc')]
1643 paths = [repo.vfs.join('hgrc')]
1644 elif opts.get('global'):
1644 elif opts.get('global'):
1645 paths = rcutil.systemrcpath()
1645 paths = rcutil.systemrcpath()
1646 else:
1646 else:
1647 paths = rcutil.userrcpath()
1647 paths = rcutil.userrcpath()
1648
1648
1649 for f in paths:
1649 for f in paths:
1650 if os.path.exists(f):
1650 if os.path.exists(f):
1651 break
1651 break
1652 else:
1652 else:
1653 if opts.get('global'):
1653 if opts.get('global'):
1654 samplehgrc = uimod.samplehgrcs['global']
1654 samplehgrc = uimod.samplehgrcs['global']
1655 elif opts.get('local'):
1655 elif opts.get('local'):
1656 samplehgrc = uimod.samplehgrcs['local']
1656 samplehgrc = uimod.samplehgrcs['local']
1657 else:
1657 else:
1658 samplehgrc = uimod.samplehgrcs['user']
1658 samplehgrc = uimod.samplehgrcs['user']
1659
1659
1660 f = paths[0]
1660 f = paths[0]
1661 fp = open(f, "wb")
1661 fp = open(f, "wb")
1662 fp.write(util.tonativeeol(samplehgrc))
1662 fp.write(util.tonativeeol(samplehgrc))
1663 fp.close()
1663 fp.close()
1664
1664
1665 editor = ui.geteditor()
1665 editor = ui.geteditor()
1666 ui.system("%s \"%s\"" % (editor, f),
1666 ui.system("%s \"%s\"" % (editor, f),
1667 onerr=error.Abort, errprefix=_("edit failed"),
1667 onerr=error.Abort, errprefix=_("edit failed"),
1668 blockedtag='config_edit')
1668 blockedtag='config_edit')
1669 return
1669 return
1670 ui.pager('config')
1670 ui.pager('config')
1671 fm = ui.formatter('config', opts)
1671 fm = ui.formatter('config', opts)
1672 for t, f in rcutil.rccomponents():
1672 for t, f in rcutil.rccomponents():
1673 if t == 'path':
1673 if t == 'path':
1674 ui.debug('read config from: %s\n' % f)
1674 ui.debug('read config from: %s\n' % f)
1675 elif t == 'items':
1675 elif t == 'items':
1676 for section, name, value, source in f:
1676 for section, name, value, source in f:
1677 ui.debug('set config by: %s\n' % source)
1677 ui.debug('set config by: %s\n' % source)
1678 else:
1678 else:
1679 raise error.ProgrammingError('unknown rctype: %s' % t)
1679 raise error.ProgrammingError('unknown rctype: %s' % t)
1680 untrusted = bool(opts.get('untrusted'))
1680 untrusted = bool(opts.get('untrusted'))
1681 if values:
1681 if values:
1682 sections = [v for v in values if '.' not in v]
1682 sections = [v for v in values if '.' not in v]
1683 items = [v for v in values if '.' in v]
1683 items = [v for v in values if '.' in v]
1684 if len(items) > 1 or items and sections:
1684 if len(items) > 1 or items and sections:
1685 raise error.Abort(_('only one config item permitted'))
1685 raise error.Abort(_('only one config item permitted'))
1686 matched = False
1686 matched = False
1687 for section, name, value in ui.walkconfig(untrusted=untrusted):
1687 for section, name, value in ui.walkconfig(untrusted=untrusted):
1688 source = ui.configsource(section, name, untrusted)
1688 source = ui.configsource(section, name, untrusted)
1689 value = pycompat.bytestr(value)
1689 value = pycompat.bytestr(value)
1690 if fm.isplain():
1690 if fm.isplain():
1691 source = source or 'none'
1691 source = source or 'none'
1692 value = value.replace('\n', '\\n')
1692 value = value.replace('\n', '\\n')
1693 entryname = section + '.' + name
1693 entryname = section + '.' + name
1694 if values:
1694 if values:
1695 for v in values:
1695 for v in values:
1696 if v == section:
1696 if v == section:
1697 fm.startitem()
1697 fm.startitem()
1698 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1698 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1699 fm.write('name value', '%s=%s\n', entryname, value)
1699 fm.write('name value', '%s=%s\n', entryname, value)
1700 matched = True
1700 matched = True
1701 elif v == entryname:
1701 elif v == entryname:
1702 fm.startitem()
1702 fm.startitem()
1703 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1703 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1704 fm.write('value', '%s\n', value)
1704 fm.write('value', '%s\n', value)
1705 fm.data(name=entryname)
1705 fm.data(name=entryname)
1706 matched = True
1706 matched = True
1707 else:
1707 else:
1708 fm.startitem()
1708 fm.startitem()
1709 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1709 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1710 fm.write('name value', '%s=%s\n', entryname, value)
1710 fm.write('name value', '%s=%s\n', entryname, value)
1711 matched = True
1711 matched = True
1712 fm.end()
1712 fm.end()
1713 if matched:
1713 if matched:
1714 return 0
1714 return 0
1715 return 1
1715 return 1
1716
1716
1717 @command('copy|cp',
1717 @command('copy|cp',
1718 [('A', 'after', None, _('record a copy that has already occurred')),
1718 [('A', 'after', None, _('record a copy that has already occurred')),
1719 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1719 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1720 ] + walkopts + dryrunopts,
1720 ] + walkopts + dryrunopts,
1721 _('[OPTION]... [SOURCE]... DEST'))
1721 _('[OPTION]... [SOURCE]... DEST'))
1722 def copy(ui, repo, *pats, **opts):
1722 def copy(ui, repo, *pats, **opts):
1723 """mark files as copied for the next commit
1723 """mark files as copied for the next commit
1724
1724
1725 Mark dest as having copies of source files. If dest is a
1725 Mark dest as having copies of source files. If dest is a
1726 directory, copies are put in that directory. If dest is a file,
1726 directory, copies are put in that directory. If dest is a file,
1727 the source must be a single file.
1727 the source must be a single file.
1728
1728
1729 By default, this command copies the contents of files as they
1729 By default, this command copies the contents of files as they
1730 exist in the working directory. If invoked with -A/--after, the
1730 exist in the working directory. If invoked with -A/--after, the
1731 operation is recorded, but no copying is performed.
1731 operation is recorded, but no copying is performed.
1732
1732
1733 This command takes effect with the next commit. To undo a copy
1733 This command takes effect with the next commit. To undo a copy
1734 before that, see :hg:`revert`.
1734 before that, see :hg:`revert`.
1735
1735
1736 Returns 0 on success, 1 if errors are encountered.
1736 Returns 0 on success, 1 if errors are encountered.
1737 """
1737 """
1738 opts = pycompat.byteskwargs(opts)
1738 opts = pycompat.byteskwargs(opts)
1739 with repo.wlock(False):
1739 with repo.wlock(False):
1740 return cmdutil.copy(ui, repo, pats, opts)
1740 return cmdutil.copy(ui, repo, pats, opts)
1741
1741
1742 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1742 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1743 def debugcommands(ui, cmd='', *args):
1743 def debugcommands(ui, cmd='', *args):
1744 """list all available commands and options"""
1744 """list all available commands and options"""
1745 for cmd, vals in sorted(table.iteritems()):
1745 for cmd, vals in sorted(table.iteritems()):
1746 cmd = cmd.split('|')[0].strip('^')
1746 cmd = cmd.split('|')[0].strip('^')
1747 opts = ', '.join([i[1] for i in vals[1]])
1747 opts = ', '.join([i[1] for i in vals[1]])
1748 ui.write('%s: %s\n' % (cmd, opts))
1748 ui.write('%s: %s\n' % (cmd, opts))
1749
1749
1750 @command('debugcomplete',
1750 @command('debugcomplete',
1751 [('o', 'options', None, _('show the command options'))],
1751 [('o', 'options', None, _('show the command options'))],
1752 _('[-o] CMD'),
1752 _('[-o] CMD'),
1753 norepo=True)
1753 norepo=True)
1754 def debugcomplete(ui, cmd='', **opts):
1754 def debugcomplete(ui, cmd='', **opts):
1755 """returns the completion list associated with the given command"""
1755 """returns the completion list associated with the given command"""
1756
1756
1757 if opts.get(r'options'):
1757 if opts.get(r'options'):
1758 options = []
1758 options = []
1759 otables = [globalopts]
1759 otables = [globalopts]
1760 if cmd:
1760 if cmd:
1761 aliases, entry = cmdutil.findcmd(cmd, table, False)
1761 aliases, entry = cmdutil.findcmd(cmd, table, False)
1762 otables.append(entry[1])
1762 otables.append(entry[1])
1763 for t in otables:
1763 for t in otables:
1764 for o in t:
1764 for o in t:
1765 if "(DEPRECATED)" in o[3]:
1765 if "(DEPRECATED)" in o[3]:
1766 continue
1766 continue
1767 if o[0]:
1767 if o[0]:
1768 options.append('-%s' % o[0])
1768 options.append('-%s' % o[0])
1769 options.append('--%s' % o[1])
1769 options.append('--%s' % o[1])
1770 ui.write("%s\n" % "\n".join(options))
1770 ui.write("%s\n" % "\n".join(options))
1771 return
1771 return
1772
1772
1773 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1773 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1774 if ui.verbose:
1774 if ui.verbose:
1775 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1775 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1776 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1776 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1777
1777
1778 @command('^diff',
1778 @command('^diff',
1779 [('r', 'rev', [], _('revision'), _('REV')),
1779 [('r', 'rev', [], _('revision'), _('REV')),
1780 ('c', 'change', '', _('change made by revision'), _('REV'))
1780 ('c', 'change', '', _('change made by revision'), _('REV'))
1781 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1781 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1782 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1782 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1783 inferrepo=True, cmdtype=readonly)
1783 inferrepo=True, cmdtype=readonly)
1784 def diff(ui, repo, *pats, **opts):
1784 def diff(ui, repo, *pats, **opts):
1785 """diff repository (or selected files)
1785 """diff repository (or selected files)
1786
1786
1787 Show differences between revisions for the specified files.
1787 Show differences between revisions for the specified files.
1788
1788
1789 Differences between files are shown using the unified diff format.
1789 Differences between files are shown using the unified diff format.
1790
1790
1791 .. note::
1791 .. note::
1792
1792
1793 :hg:`diff` may generate unexpected results for merges, as it will
1793 :hg:`diff` may generate unexpected results for merges, as it will
1794 default to comparing against the working directory's first
1794 default to comparing against the working directory's first
1795 parent changeset if no revisions are specified.
1795 parent changeset if no revisions are specified.
1796
1796
1797 When two revision arguments are given, then changes are shown
1797 When two revision arguments are given, then changes are shown
1798 between those revisions. If only one revision is specified then
1798 between those revisions. If only one revision is specified then
1799 that revision is compared to the working directory, and, when no
1799 that revision is compared to the working directory, and, when no
1800 revisions are specified, the working directory files are compared
1800 revisions are specified, the working directory files are compared
1801 to its first parent.
1801 to its first parent.
1802
1802
1803 Alternatively you can specify -c/--change with a revision to see
1803 Alternatively you can specify -c/--change with a revision to see
1804 the changes in that changeset relative to its first parent.
1804 the changes in that changeset relative to its first parent.
1805
1805
1806 Without the -a/--text option, diff will avoid generating diffs of
1806 Without the -a/--text option, diff will avoid generating diffs of
1807 files it detects as binary. With -a, diff will generate a diff
1807 files it detects as binary. With -a, diff will generate a diff
1808 anyway, probably with undesirable results.
1808 anyway, probably with undesirable results.
1809
1809
1810 Use the -g/--git option to generate diffs in the git extended diff
1810 Use the -g/--git option to generate diffs in the git extended diff
1811 format. For more information, read :hg:`help diffs`.
1811 format. For more information, read :hg:`help diffs`.
1812
1812
1813 .. container:: verbose
1813 .. container:: verbose
1814
1814
1815 Examples:
1815 Examples:
1816
1816
1817 - compare a file in the current working directory to its parent::
1817 - compare a file in the current working directory to its parent::
1818
1818
1819 hg diff foo.c
1819 hg diff foo.c
1820
1820
1821 - compare two historical versions of a directory, with rename info::
1821 - compare two historical versions of a directory, with rename info::
1822
1822
1823 hg diff --git -r 1.0:1.2 lib/
1823 hg diff --git -r 1.0:1.2 lib/
1824
1824
1825 - get change stats relative to the last change on some date::
1825 - get change stats relative to the last change on some date::
1826
1826
1827 hg diff --stat -r "date('may 2')"
1827 hg diff --stat -r "date('may 2')"
1828
1828
1829 - diff all newly-added files that contain a keyword::
1829 - diff all newly-added files that contain a keyword::
1830
1830
1831 hg diff "set:added() and grep(GNU)"
1831 hg diff "set:added() and grep(GNU)"
1832
1832
1833 - compare a revision and its parents::
1833 - compare a revision and its parents::
1834
1834
1835 hg diff -c 9353 # compare against first parent
1835 hg diff -c 9353 # compare against first parent
1836 hg diff -r 9353^:9353 # same using revset syntax
1836 hg diff -r 9353^:9353 # same using revset syntax
1837 hg diff -r 9353^2:9353 # compare against the second parent
1837 hg diff -r 9353^2:9353 # compare against the second parent
1838
1838
1839 Returns 0 on success.
1839 Returns 0 on success.
1840 """
1840 """
1841
1841
1842 opts = pycompat.byteskwargs(opts)
1842 opts = pycompat.byteskwargs(opts)
1843 revs = opts.get('rev')
1843 revs = opts.get('rev')
1844 change = opts.get('change')
1844 change = opts.get('change')
1845 stat = opts.get('stat')
1845 stat = opts.get('stat')
1846 reverse = opts.get('reverse')
1846 reverse = opts.get('reverse')
1847
1847
1848 if revs and change:
1848 if revs and change:
1849 msg = _('cannot specify --rev and --change at the same time')
1849 msg = _('cannot specify --rev and --change at the same time')
1850 raise error.Abort(msg)
1850 raise error.Abort(msg)
1851 elif change:
1851 elif change:
1852 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
1852 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
1853 node2 = scmutil.revsingle(repo, change, None).node()
1853 node2 = scmutil.revsingle(repo, change, None).node()
1854 node1 = repo[node2].p1().node()
1854 node1 = repo[node2].p1().node()
1855 else:
1855 else:
1856 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
1856 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
1857 node1, node2 = scmutil.revpair(repo, revs)
1857 node1, node2 = scmutil.revpair(repo, revs)
1858
1858
1859 if reverse:
1859 if reverse:
1860 node1, node2 = node2, node1
1860 node1, node2 = node2, node1
1861
1861
1862 diffopts = patch.diffallopts(ui, opts)
1862 diffopts = patch.diffallopts(ui, opts)
1863 m = scmutil.match(repo[node2], pats, opts)
1863 m = scmutil.match(repo[node2], pats, opts)
1864 ui.pager('diff')
1864 ui.pager('diff')
1865 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1865 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1866 listsubrepos=opts.get('subrepos'),
1866 listsubrepos=opts.get('subrepos'),
1867 root=opts.get('root'))
1867 root=opts.get('root'))
1868
1868
1869 @command('^export',
1869 @command('^export',
1870 [('o', 'output', '',
1870 [('o', 'output', '',
1871 _('print output to file with formatted name'), _('FORMAT')),
1871 _('print output to file with formatted name'), _('FORMAT')),
1872 ('', 'switch-parent', None, _('diff against the second parent')),
1872 ('', 'switch-parent', None, _('diff against the second parent')),
1873 ('r', 'rev', [], _('revisions to export'), _('REV')),
1873 ('r', 'rev', [], _('revisions to export'), _('REV')),
1874 ] + diffopts,
1874 ] + diffopts,
1875 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'), cmdtype=readonly)
1875 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'), cmdtype=readonly)
1876 def export(ui, repo, *changesets, **opts):
1876 def export(ui, repo, *changesets, **opts):
1877 """dump the header and diffs for one or more changesets
1877 """dump the header and diffs for one or more changesets
1878
1878
1879 Print the changeset header and diffs for one or more revisions.
1879 Print the changeset header and diffs for one or more revisions.
1880 If no revision is given, the parent of the working directory is used.
1880 If no revision is given, the parent of the working directory is used.
1881
1881
1882 The information shown in the changeset header is: author, date,
1882 The information shown in the changeset header is: author, date,
1883 branch name (if non-default), changeset hash, parent(s) and commit
1883 branch name (if non-default), changeset hash, parent(s) and commit
1884 comment.
1884 comment.
1885
1885
1886 .. note::
1886 .. note::
1887
1887
1888 :hg:`export` may generate unexpected diff output for merge
1888 :hg:`export` may generate unexpected diff output for merge
1889 changesets, as it will compare the merge changeset against its
1889 changesets, as it will compare the merge changeset against its
1890 first parent only.
1890 first parent only.
1891
1891
1892 Output may be to a file, in which case the name of the file is
1892 Output may be to a file, in which case the name of the file is
1893 given using a format string. The formatting rules are as follows:
1893 given using a format string. The formatting rules are as follows:
1894
1894
1895 :``%%``: literal "%" character
1895 :``%%``: literal "%" character
1896 :``%H``: changeset hash (40 hexadecimal digits)
1896 :``%H``: changeset hash (40 hexadecimal digits)
1897 :``%N``: number of patches being generated
1897 :``%N``: number of patches being generated
1898 :``%R``: changeset revision number
1898 :``%R``: changeset revision number
1899 :``%b``: basename of the exporting repository
1899 :``%b``: basename of the exporting repository
1900 :``%h``: short-form changeset hash (12 hexadecimal digits)
1900 :``%h``: short-form changeset hash (12 hexadecimal digits)
1901 :``%m``: first line of the commit message (only alphanumeric characters)
1901 :``%m``: first line of the commit message (only alphanumeric characters)
1902 :``%n``: zero-padded sequence number, starting at 1
1902 :``%n``: zero-padded sequence number, starting at 1
1903 :``%r``: zero-padded changeset revision number
1903 :``%r``: zero-padded changeset revision number
1904
1904
1905 Without the -a/--text option, export will avoid generating diffs
1905 Without the -a/--text option, export will avoid generating diffs
1906 of files it detects as binary. With -a, export will generate a
1906 of files it detects as binary. With -a, export will generate a
1907 diff anyway, probably with undesirable results.
1907 diff anyway, probably with undesirable results.
1908
1908
1909 Use the -g/--git option to generate diffs in the git extended diff
1909 Use the -g/--git option to generate diffs in the git extended diff
1910 format. See :hg:`help diffs` for more information.
1910 format. See :hg:`help diffs` for more information.
1911
1911
1912 With the --switch-parent option, the diff will be against the
1912 With the --switch-parent option, the diff will be against the
1913 second parent. It can be useful to review a merge.
1913 second parent. It can be useful to review a merge.
1914
1914
1915 .. container:: verbose
1915 .. container:: verbose
1916
1916
1917 Examples:
1917 Examples:
1918
1918
1919 - use export and import to transplant a bugfix to the current
1919 - use export and import to transplant a bugfix to the current
1920 branch::
1920 branch::
1921
1921
1922 hg export -r 9353 | hg import -
1922 hg export -r 9353 | hg import -
1923
1923
1924 - export all the changesets between two revisions to a file with
1924 - export all the changesets between two revisions to a file with
1925 rename information::
1925 rename information::
1926
1926
1927 hg export --git -r 123:150 > changes.txt
1927 hg export --git -r 123:150 > changes.txt
1928
1928
1929 - split outgoing changes into a series of patches with
1929 - split outgoing changes into a series of patches with
1930 descriptive names::
1930 descriptive names::
1931
1931
1932 hg export -r "outgoing()" -o "%n-%m.patch"
1932 hg export -r "outgoing()" -o "%n-%m.patch"
1933
1933
1934 Returns 0 on success.
1934 Returns 0 on success.
1935 """
1935 """
1936 opts = pycompat.byteskwargs(opts)
1936 opts = pycompat.byteskwargs(opts)
1937 changesets += tuple(opts.get('rev', []))
1937 changesets += tuple(opts.get('rev', []))
1938 if not changesets:
1938 if not changesets:
1939 changesets = ['.']
1939 changesets = ['.']
1940 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
1940 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
1941 revs = scmutil.revrange(repo, changesets)
1941 revs = scmutil.revrange(repo, changesets)
1942 if not revs:
1942 if not revs:
1943 raise error.Abort(_("export requires at least one changeset"))
1943 raise error.Abort(_("export requires at least one changeset"))
1944 if len(revs) > 1:
1944 if len(revs) > 1:
1945 ui.note(_('exporting patches:\n'))
1945 ui.note(_('exporting patches:\n'))
1946 else:
1946 else:
1947 ui.note(_('exporting patch:\n'))
1947 ui.note(_('exporting patch:\n'))
1948 ui.pager('export')
1948 ui.pager('export')
1949 cmdutil.export(repo, revs, fntemplate=opts.get('output'),
1949 cmdutil.export(repo, revs, fntemplate=opts.get('output'),
1950 switch_parent=opts.get('switch_parent'),
1950 switch_parent=opts.get('switch_parent'),
1951 opts=patch.diffallopts(ui, opts))
1951 opts=patch.diffallopts(ui, opts))
1952
1952
1953 @command('files',
1953 @command('files',
1954 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
1954 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
1955 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
1955 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
1956 ] + walkopts + formatteropts + subrepoopts,
1956 ] + walkopts + formatteropts + subrepoopts,
1957 _('[OPTION]... [FILE]...'), cmdtype=readonly)
1957 _('[OPTION]... [FILE]...'), cmdtype=readonly)
1958 def files(ui, repo, *pats, **opts):
1958 def files(ui, repo, *pats, **opts):
1959 """list tracked files
1959 """list tracked files
1960
1960
1961 Print files under Mercurial control in the working directory or
1961 Print files under Mercurial control in the working directory or
1962 specified revision for given files (excluding removed files).
1962 specified revision for given files (excluding removed files).
1963 Files can be specified as filenames or filesets.
1963 Files can be specified as filenames or filesets.
1964
1964
1965 If no files are given to match, this command prints the names
1965 If no files are given to match, this command prints the names
1966 of all files under Mercurial control.
1966 of all files under Mercurial control.
1967
1967
1968 .. container:: verbose
1968 .. container:: verbose
1969
1969
1970 Examples:
1970 Examples:
1971
1971
1972 - list all files under the current directory::
1972 - list all files under the current directory::
1973
1973
1974 hg files .
1974 hg files .
1975
1975
1976 - shows sizes and flags for current revision::
1976 - shows sizes and flags for current revision::
1977
1977
1978 hg files -vr .
1978 hg files -vr .
1979
1979
1980 - list all files named README::
1980 - list all files named README::
1981
1981
1982 hg files -I "**/README"
1982 hg files -I "**/README"
1983
1983
1984 - list all binary files::
1984 - list all binary files::
1985
1985
1986 hg files "set:binary()"
1986 hg files "set:binary()"
1987
1987
1988 - find files containing a regular expression::
1988 - find files containing a regular expression::
1989
1989
1990 hg files "set:grep('bob')"
1990 hg files "set:grep('bob')"
1991
1991
1992 - search tracked file contents with xargs and grep::
1992 - search tracked file contents with xargs and grep::
1993
1993
1994 hg files -0 | xargs -0 grep foo
1994 hg files -0 | xargs -0 grep foo
1995
1995
1996 See :hg:`help patterns` and :hg:`help filesets` for more information
1996 See :hg:`help patterns` and :hg:`help filesets` for more information
1997 on specifying file patterns.
1997 on specifying file patterns.
1998
1998
1999 Returns 0 if a match is found, 1 otherwise.
1999 Returns 0 if a match is found, 1 otherwise.
2000
2000
2001 """
2001 """
2002
2002
2003 opts = pycompat.byteskwargs(opts)
2003 opts = pycompat.byteskwargs(opts)
2004 rev = opts.get('rev')
2004 rev = opts.get('rev')
2005 if rev:
2005 if rev:
2006 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2006 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2007 ctx = scmutil.revsingle(repo, rev, None)
2007 ctx = scmutil.revsingle(repo, rev, None)
2008
2008
2009 end = '\n'
2009 end = '\n'
2010 if opts.get('print0'):
2010 if opts.get('print0'):
2011 end = '\0'
2011 end = '\0'
2012 fmt = '%s' + end
2012 fmt = '%s' + end
2013
2013
2014 m = scmutil.match(ctx, pats, opts)
2014 m = scmutil.match(ctx, pats, opts)
2015 ui.pager('files')
2015 ui.pager('files')
2016 with ui.formatter('files', opts) as fm:
2016 with ui.formatter('files', opts) as fm:
2017 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2017 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2018
2018
2019 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
2019 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
2020 def forget(ui, repo, *pats, **opts):
2020 def forget(ui, repo, *pats, **opts):
2021 """forget the specified files on the next commit
2021 """forget the specified files on the next commit
2022
2022
2023 Mark the specified files so they will no longer be tracked
2023 Mark the specified files so they will no longer be tracked
2024 after the next commit.
2024 after the next commit.
2025
2025
2026 This only removes files from the current branch, not from the
2026 This only removes files from the current branch, not from the
2027 entire project history, and it does not delete them from the
2027 entire project history, and it does not delete them from the
2028 working directory.
2028 working directory.
2029
2029
2030 To delete the file from the working directory, see :hg:`remove`.
2030 To delete the file from the working directory, see :hg:`remove`.
2031
2031
2032 To undo a forget before the next commit, see :hg:`add`.
2032 To undo a forget before the next commit, see :hg:`add`.
2033
2033
2034 .. container:: verbose
2034 .. container:: verbose
2035
2035
2036 Examples:
2036 Examples:
2037
2037
2038 - forget newly-added binary files::
2038 - forget newly-added binary files::
2039
2039
2040 hg forget "set:added() and binary()"
2040 hg forget "set:added() and binary()"
2041
2041
2042 - forget files that would be excluded by .hgignore::
2042 - forget files that would be excluded by .hgignore::
2043
2043
2044 hg forget "set:hgignore()"
2044 hg forget "set:hgignore()"
2045
2045
2046 Returns 0 on success.
2046 Returns 0 on success.
2047 """
2047 """
2048
2048
2049 opts = pycompat.byteskwargs(opts)
2049 opts = pycompat.byteskwargs(opts)
2050 if not pats:
2050 if not pats:
2051 raise error.Abort(_('no files specified'))
2051 raise error.Abort(_('no files specified'))
2052
2052
2053 m = scmutil.match(repo[None], pats, opts)
2053 m = scmutil.match(repo[None], pats, opts)
2054 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2054 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2055 return rejected and 1 or 0
2055 return rejected and 1 or 0
2056
2056
2057 @command(
2057 @command(
2058 'graft',
2058 'graft',
2059 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2059 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2060 ('c', 'continue', False, _('resume interrupted graft')),
2060 ('c', 'continue', False, _('resume interrupted graft')),
2061 ('e', 'edit', False, _('invoke editor on commit messages')),
2061 ('e', 'edit', False, _('invoke editor on commit messages')),
2062 ('', 'log', None, _('append graft info to log message')),
2062 ('', 'log', None, _('append graft info to log message')),
2063 ('f', 'force', False, _('force graft')),
2063 ('f', 'force', False, _('force graft')),
2064 ('D', 'currentdate', False,
2064 ('D', 'currentdate', False,
2065 _('record the current date as commit date')),
2065 _('record the current date as commit date')),
2066 ('U', 'currentuser', False,
2066 ('U', 'currentuser', False,
2067 _('record the current user as committer'), _('DATE'))]
2067 _('record the current user as committer'), _('DATE'))]
2068 + commitopts2 + mergetoolopts + dryrunopts,
2068 + commitopts2 + mergetoolopts + dryrunopts,
2069 _('[OPTION]... [-r REV]... REV...'))
2069 _('[OPTION]... [-r REV]... REV...'))
2070 def graft(ui, repo, *revs, **opts):
2070 def graft(ui, repo, *revs, **opts):
2071 '''copy changes from other branches onto the current branch
2071 '''copy changes from other branches onto the current branch
2072
2072
2073 This command uses Mercurial's merge logic to copy individual
2073 This command uses Mercurial's merge logic to copy individual
2074 changes from other branches without merging branches in the
2074 changes from other branches without merging branches in the
2075 history graph. This is sometimes known as 'backporting' or
2075 history graph. This is sometimes known as 'backporting' or
2076 'cherry-picking'. By default, graft will copy user, date, and
2076 'cherry-picking'. By default, graft will copy user, date, and
2077 description from the source changesets.
2077 description from the source changesets.
2078
2078
2079 Changesets that are ancestors of the current revision, that have
2079 Changesets that are ancestors of the current revision, that have
2080 already been grafted, or that are merges will be skipped.
2080 already been grafted, or that are merges will be skipped.
2081
2081
2082 If --log is specified, log messages will have a comment appended
2082 If --log is specified, log messages will have a comment appended
2083 of the form::
2083 of the form::
2084
2084
2085 (grafted from CHANGESETHASH)
2085 (grafted from CHANGESETHASH)
2086
2086
2087 If --force is specified, revisions will be grafted even if they
2087 If --force is specified, revisions will be grafted even if they
2088 are already ancestors of, or have been grafted to, the destination.
2088 are already ancestors of, or have been grafted to, the destination.
2089 This is useful when the revisions have since been backed out.
2089 This is useful when the revisions have since been backed out.
2090
2090
2091 If a graft merge results in conflicts, the graft process is
2091 If a graft merge results in conflicts, the graft process is
2092 interrupted so that the current merge can be manually resolved.
2092 interrupted so that the current merge can be manually resolved.
2093 Once all conflicts are addressed, the graft process can be
2093 Once all conflicts are addressed, the graft process can be
2094 continued with the -c/--continue option.
2094 continued with the -c/--continue option.
2095
2095
2096 .. note::
2096 .. note::
2097
2097
2098 The -c/--continue option does not reapply earlier options, except
2098 The -c/--continue option does not reapply earlier options, except
2099 for --force.
2099 for --force.
2100
2100
2101 .. container:: verbose
2101 .. container:: verbose
2102
2102
2103 Examples:
2103 Examples:
2104
2104
2105 - copy a single change to the stable branch and edit its description::
2105 - copy a single change to the stable branch and edit its description::
2106
2106
2107 hg update stable
2107 hg update stable
2108 hg graft --edit 9393
2108 hg graft --edit 9393
2109
2109
2110 - graft a range of changesets with one exception, updating dates::
2110 - graft a range of changesets with one exception, updating dates::
2111
2111
2112 hg graft -D "2085::2093 and not 2091"
2112 hg graft -D "2085::2093 and not 2091"
2113
2113
2114 - continue a graft after resolving conflicts::
2114 - continue a graft after resolving conflicts::
2115
2115
2116 hg graft -c
2116 hg graft -c
2117
2117
2118 - show the source of a grafted changeset::
2118 - show the source of a grafted changeset::
2119
2119
2120 hg log --debug -r .
2120 hg log --debug -r .
2121
2121
2122 - show revisions sorted by date::
2122 - show revisions sorted by date::
2123
2123
2124 hg log -r "sort(all(), date)"
2124 hg log -r "sort(all(), date)"
2125
2125
2126 See :hg:`help revisions` for more about specifying revisions.
2126 See :hg:`help revisions` for more about specifying revisions.
2127
2127
2128 Returns 0 on successful completion.
2128 Returns 0 on successful completion.
2129 '''
2129 '''
2130 with repo.wlock():
2130 with repo.wlock():
2131 return _dograft(ui, repo, *revs, **opts)
2131 return _dograft(ui, repo, *revs, **opts)
2132
2132
2133 def _dograft(ui, repo, *revs, **opts):
2133 def _dograft(ui, repo, *revs, **opts):
2134 opts = pycompat.byteskwargs(opts)
2134 opts = pycompat.byteskwargs(opts)
2135 if revs and opts.get('rev'):
2135 if revs and opts.get('rev'):
2136 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2136 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2137 'revision ordering!\n'))
2137 'revision ordering!\n'))
2138
2138
2139 revs = list(revs)
2139 revs = list(revs)
2140 revs.extend(opts.get('rev'))
2140 revs.extend(opts.get('rev'))
2141
2141
2142 if not opts.get('user') and opts.get('currentuser'):
2142 if not opts.get('user') and opts.get('currentuser'):
2143 opts['user'] = ui.username()
2143 opts['user'] = ui.username()
2144 if not opts.get('date') and opts.get('currentdate'):
2144 if not opts.get('date') and opts.get('currentdate'):
2145 opts['date'] = "%d %d" % util.makedate()
2145 opts['date'] = "%d %d" % util.makedate()
2146
2146
2147 editor = cmdutil.getcommiteditor(editform='graft',
2147 editor = cmdutil.getcommiteditor(editform='graft',
2148 **pycompat.strkwargs(opts))
2148 **pycompat.strkwargs(opts))
2149
2149
2150 cont = False
2150 cont = False
2151 if opts.get('continue'):
2151 if opts.get('continue'):
2152 cont = True
2152 cont = True
2153 if revs:
2153 if revs:
2154 raise error.Abort(_("can't specify --continue and revisions"))
2154 raise error.Abort(_("can't specify --continue and revisions"))
2155 # read in unfinished revisions
2155 # read in unfinished revisions
2156 try:
2156 try:
2157 nodes = repo.vfs.read('graftstate').splitlines()
2157 nodes = repo.vfs.read('graftstate').splitlines()
2158 revs = [repo[node].rev() for node in nodes]
2158 revs = [repo[node].rev() for node in nodes]
2159 except IOError as inst:
2159 except IOError as inst:
2160 if inst.errno != errno.ENOENT:
2160 if inst.errno != errno.ENOENT:
2161 raise
2161 raise
2162 cmdutil.wrongtooltocontinue(repo, _('graft'))
2162 cmdutil.wrongtooltocontinue(repo, _('graft'))
2163 else:
2163 else:
2164 cmdutil.checkunfinished(repo)
2164 cmdutil.checkunfinished(repo)
2165 cmdutil.bailifchanged(repo)
2165 cmdutil.bailifchanged(repo)
2166 if not revs:
2166 if not revs:
2167 raise error.Abort(_('no revisions specified'))
2167 raise error.Abort(_('no revisions specified'))
2168 revs = scmutil.revrange(repo, revs)
2168 revs = scmutil.revrange(repo, revs)
2169
2169
2170 skipped = set()
2170 skipped = set()
2171 # check for merges
2171 # check for merges
2172 for rev in repo.revs('%ld and merge()', revs):
2172 for rev in repo.revs('%ld and merge()', revs):
2173 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2173 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2174 skipped.add(rev)
2174 skipped.add(rev)
2175 revs = [r for r in revs if r not in skipped]
2175 revs = [r for r in revs if r not in skipped]
2176 if not revs:
2176 if not revs:
2177 return -1
2177 return -1
2178
2178
2179 # Don't check in the --continue case, in effect retaining --force across
2179 # Don't check in the --continue case, in effect retaining --force across
2180 # --continues. That's because without --force, any revisions we decided to
2180 # --continues. That's because without --force, any revisions we decided to
2181 # skip would have been filtered out here, so they wouldn't have made their
2181 # skip would have been filtered out here, so they wouldn't have made their
2182 # way to the graftstate. With --force, any revisions we would have otherwise
2182 # way to the graftstate. With --force, any revisions we would have otherwise
2183 # skipped would not have been filtered out, and if they hadn't been applied
2183 # skipped would not have been filtered out, and if they hadn't been applied
2184 # already, they'd have been in the graftstate.
2184 # already, they'd have been in the graftstate.
2185 if not (cont or opts.get('force')):
2185 if not (cont or opts.get('force')):
2186 # check for ancestors of dest branch
2186 # check for ancestors of dest branch
2187 crev = repo['.'].rev()
2187 crev = repo['.'].rev()
2188 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2188 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2189 # XXX make this lazy in the future
2189 # XXX make this lazy in the future
2190 # don't mutate while iterating, create a copy
2190 # don't mutate while iterating, create a copy
2191 for rev in list(revs):
2191 for rev in list(revs):
2192 if rev in ancestors:
2192 if rev in ancestors:
2193 ui.warn(_('skipping ancestor revision %d:%s\n') %
2193 ui.warn(_('skipping ancestor revision %d:%s\n') %
2194 (rev, repo[rev]))
2194 (rev, repo[rev]))
2195 # XXX remove on list is slow
2195 # XXX remove on list is slow
2196 revs.remove(rev)
2196 revs.remove(rev)
2197 if not revs:
2197 if not revs:
2198 return -1
2198 return -1
2199
2199
2200 # analyze revs for earlier grafts
2200 # analyze revs for earlier grafts
2201 ids = {}
2201 ids = {}
2202 for ctx in repo.set("%ld", revs):
2202 for ctx in repo.set("%ld", revs):
2203 ids[ctx.hex()] = ctx.rev()
2203 ids[ctx.hex()] = ctx.rev()
2204 n = ctx.extra().get('source')
2204 n = ctx.extra().get('source')
2205 if n:
2205 if n:
2206 ids[n] = ctx.rev()
2206 ids[n] = ctx.rev()
2207
2207
2208 # check ancestors for earlier grafts
2208 # check ancestors for earlier grafts
2209 ui.debug('scanning for duplicate grafts\n')
2209 ui.debug('scanning for duplicate grafts\n')
2210
2210
2211 # The only changesets we can be sure doesn't contain grafts of any
2211 # The only changesets we can be sure doesn't contain grafts of any
2212 # revs, are the ones that are common ancestors of *all* revs:
2212 # revs, are the ones that are common ancestors of *all* revs:
2213 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2213 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2214 ctx = repo[rev]
2214 ctx = repo[rev]
2215 n = ctx.extra().get('source')
2215 n = ctx.extra().get('source')
2216 if n in ids:
2216 if n in ids:
2217 try:
2217 try:
2218 r = repo[n].rev()
2218 r = repo[n].rev()
2219 except error.RepoLookupError:
2219 except error.RepoLookupError:
2220 r = None
2220 r = None
2221 if r in revs:
2221 if r in revs:
2222 ui.warn(_('skipping revision %d:%s '
2222 ui.warn(_('skipping revision %d:%s '
2223 '(already grafted to %d:%s)\n')
2223 '(already grafted to %d:%s)\n')
2224 % (r, repo[r], rev, ctx))
2224 % (r, repo[r], rev, ctx))
2225 revs.remove(r)
2225 revs.remove(r)
2226 elif ids[n] in revs:
2226 elif ids[n] in revs:
2227 if r is None:
2227 if r is None:
2228 ui.warn(_('skipping already grafted revision %d:%s '
2228 ui.warn(_('skipping already grafted revision %d:%s '
2229 '(%d:%s also has unknown origin %s)\n')
2229 '(%d:%s also has unknown origin %s)\n')
2230 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2230 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2231 else:
2231 else:
2232 ui.warn(_('skipping already grafted revision %d:%s '
2232 ui.warn(_('skipping already grafted revision %d:%s '
2233 '(%d:%s also has origin %d:%s)\n')
2233 '(%d:%s also has origin %d:%s)\n')
2234 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2234 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2235 revs.remove(ids[n])
2235 revs.remove(ids[n])
2236 elif ctx.hex() in ids:
2236 elif ctx.hex() in ids:
2237 r = ids[ctx.hex()]
2237 r = ids[ctx.hex()]
2238 ui.warn(_('skipping already grafted revision %d:%s '
2238 ui.warn(_('skipping already grafted revision %d:%s '
2239 '(was grafted from %d:%s)\n') %
2239 '(was grafted from %d:%s)\n') %
2240 (r, repo[r], rev, ctx))
2240 (r, repo[r], rev, ctx))
2241 revs.remove(r)
2241 revs.remove(r)
2242 if not revs:
2242 if not revs:
2243 return -1
2243 return -1
2244
2244
2245 for pos, ctx in enumerate(repo.set("%ld", revs)):
2245 for pos, ctx in enumerate(repo.set("%ld", revs)):
2246 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2246 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2247 ctx.description().split('\n', 1)[0])
2247 ctx.description().split('\n', 1)[0])
2248 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2248 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2249 if names:
2249 if names:
2250 desc += ' (%s)' % ' '.join(names)
2250 desc += ' (%s)' % ' '.join(names)
2251 ui.status(_('grafting %s\n') % desc)
2251 ui.status(_('grafting %s\n') % desc)
2252 if opts.get('dry_run'):
2252 if opts.get('dry_run'):
2253 continue
2253 continue
2254
2254
2255 source = ctx.extra().get('source')
2255 source = ctx.extra().get('source')
2256 extra = {}
2256 extra = {}
2257 if source:
2257 if source:
2258 extra['source'] = source
2258 extra['source'] = source
2259 extra['intermediate-source'] = ctx.hex()
2259 extra['intermediate-source'] = ctx.hex()
2260 else:
2260 else:
2261 extra['source'] = ctx.hex()
2261 extra['source'] = ctx.hex()
2262 user = ctx.user()
2262 user = ctx.user()
2263 if opts.get('user'):
2263 if opts.get('user'):
2264 user = opts['user']
2264 user = opts['user']
2265 date = ctx.date()
2265 date = ctx.date()
2266 if opts.get('date'):
2266 if opts.get('date'):
2267 date = opts['date']
2267 date = opts['date']
2268 message = ctx.description()
2268 message = ctx.description()
2269 if opts.get('log'):
2269 if opts.get('log'):
2270 message += '\n(grafted from %s)' % ctx.hex()
2270 message += '\n(grafted from %s)' % ctx.hex()
2271
2271
2272 # we don't merge the first commit when continuing
2272 # we don't merge the first commit when continuing
2273 if not cont:
2273 if not cont:
2274 # perform the graft merge with p1(rev) as 'ancestor'
2274 # perform the graft merge with p1(rev) as 'ancestor'
2275 try:
2275 try:
2276 # ui.forcemerge is an internal variable, do not document
2276 # ui.forcemerge is an internal variable, do not document
2277 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
2277 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
2278 'graft')
2278 'graft')
2279 stats = mergemod.graft(repo, ctx, ctx.p1(),
2279 stats = mergemod.graft(repo, ctx, ctx.p1(),
2280 ['local', 'graft'])
2280 ['local', 'graft'])
2281 finally:
2281 finally:
2282 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
2282 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
2283 # report any conflicts
2283 # report any conflicts
2284 if stats and stats[3] > 0:
2284 if stats and stats[3] > 0:
2285 # write out state for --continue
2285 # write out state for --continue
2286 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2286 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2287 repo.vfs.write('graftstate', ''.join(nodelines))
2287 repo.vfs.write('graftstate', ''.join(nodelines))
2288 extra = ''
2288 extra = ''
2289 if opts.get('user'):
2289 if opts.get('user'):
2290 extra += ' --user %s' % util.shellquote(opts['user'])
2290 extra += ' --user %s' % util.shellquote(opts['user'])
2291 if opts.get('date'):
2291 if opts.get('date'):
2292 extra += ' --date %s' % util.shellquote(opts['date'])
2292 extra += ' --date %s' % util.shellquote(opts['date'])
2293 if opts.get('log'):
2293 if opts.get('log'):
2294 extra += ' --log'
2294 extra += ' --log'
2295 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
2295 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
2296 raise error.Abort(
2296 raise error.Abort(
2297 _("unresolved conflicts, can't continue"),
2297 _("unresolved conflicts, can't continue"),
2298 hint=hint)
2298 hint=hint)
2299 else:
2299 else:
2300 cont = False
2300 cont = False
2301
2301
2302 # commit
2302 # commit
2303 node = repo.commit(text=message, user=user,
2303 node = repo.commit(text=message, user=user,
2304 date=date, extra=extra, editor=editor)
2304 date=date, extra=extra, editor=editor)
2305 if node is None:
2305 if node is None:
2306 ui.warn(
2306 ui.warn(
2307 _('note: graft of %d:%s created no changes to commit\n') %
2307 _('note: graft of %d:%s created no changes to commit\n') %
2308 (ctx.rev(), ctx))
2308 (ctx.rev(), ctx))
2309
2309
2310 # remove state when we complete successfully
2310 # remove state when we complete successfully
2311 if not opts.get('dry_run'):
2311 if not opts.get('dry_run'):
2312 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
2312 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
2313
2313
2314 return 0
2314 return 0
2315
2315
2316 @command('grep',
2316 @command('grep',
2317 [('0', 'print0', None, _('end fields with NUL')),
2317 [('0', 'print0', None, _('end fields with NUL')),
2318 ('', 'all', None, _('print all revisions that match')),
2318 ('', 'all', None, _('print all revisions that match')),
2319 ('a', 'text', None, _('treat all files as text')),
2319 ('a', 'text', None, _('treat all files as text')),
2320 ('f', 'follow', None,
2320 ('f', 'follow', None,
2321 _('follow changeset history,'
2321 _('follow changeset history,'
2322 ' or file history across copies and renames')),
2322 ' or file history across copies and renames')),
2323 ('i', 'ignore-case', None, _('ignore case when matching')),
2323 ('i', 'ignore-case', None, _('ignore case when matching')),
2324 ('l', 'files-with-matches', None,
2324 ('l', 'files-with-matches', None,
2325 _('print only filenames and revisions that match')),
2325 _('print only filenames and revisions that match')),
2326 ('n', 'line-number', None, _('print matching line numbers')),
2326 ('n', 'line-number', None, _('print matching line numbers')),
2327 ('r', 'rev', [],
2327 ('r', 'rev', [],
2328 _('only search files changed within revision range'), _('REV')),
2328 _('only search files changed within revision range'), _('REV')),
2329 ('u', 'user', None, _('list the author (long with -v)')),
2329 ('u', 'user', None, _('list the author (long with -v)')),
2330 ('d', 'date', None, _('list the date (short with -q)')),
2330 ('d', 'date', None, _('list the date (short with -q)')),
2331 ] + formatteropts + walkopts,
2331 ] + formatteropts + walkopts,
2332 _('[OPTION]... PATTERN [FILE]...'),
2332 _('[OPTION]... PATTERN [FILE]...'),
2333 inferrepo=True, cmdtype=readonly)
2333 inferrepo=True, cmdtype=readonly)
2334 def grep(ui, repo, pattern, *pats, **opts):
2334 def grep(ui, repo, pattern, *pats, **opts):
2335 """search revision history for a pattern in specified files
2335 """search revision history for a pattern in specified files
2336
2336
2337 Search revision history for a regular expression in the specified
2337 Search revision history for a regular expression in the specified
2338 files or the entire project.
2338 files or the entire project.
2339
2339
2340 By default, grep prints the most recent revision number for each
2340 By default, grep prints the most recent revision number for each
2341 file in which it finds a match. To get it to print every revision
2341 file in which it finds a match. To get it to print every revision
2342 that contains a change in match status ("-" for a match that becomes
2342 that contains a change in match status ("-" for a match that becomes
2343 a non-match, or "+" for a non-match that becomes a match), use the
2343 a non-match, or "+" for a non-match that becomes a match), use the
2344 --all flag.
2344 --all flag.
2345
2345
2346 PATTERN can be any Python (roughly Perl-compatible) regular
2346 PATTERN can be any Python (roughly Perl-compatible) regular
2347 expression.
2347 expression.
2348
2348
2349 If no FILEs are specified (and -f/--follow isn't set), all files in
2349 If no FILEs are specified (and -f/--follow isn't set), all files in
2350 the repository are searched, including those that don't exist in the
2350 the repository are searched, including those that don't exist in the
2351 current branch or have been deleted in a prior changeset.
2351 current branch or have been deleted in a prior changeset.
2352
2352
2353 Returns 0 if a match is found, 1 otherwise.
2353 Returns 0 if a match is found, 1 otherwise.
2354 """
2354 """
2355 opts = pycompat.byteskwargs(opts)
2355 opts = pycompat.byteskwargs(opts)
2356 reflags = re.M
2356 reflags = re.M
2357 if opts.get('ignore_case'):
2357 if opts.get('ignore_case'):
2358 reflags |= re.I
2358 reflags |= re.I
2359 try:
2359 try:
2360 regexp = util.re.compile(pattern, reflags)
2360 regexp = util.re.compile(pattern, reflags)
2361 except re.error as inst:
2361 except re.error as inst:
2362 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2362 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2363 return 1
2363 return 1
2364 sep, eol = ':', '\n'
2364 sep, eol = ':', '\n'
2365 if opts.get('print0'):
2365 if opts.get('print0'):
2366 sep = eol = '\0'
2366 sep = eol = '\0'
2367
2367
2368 getfile = util.lrucachefunc(repo.file)
2368 getfile = util.lrucachefunc(repo.file)
2369
2369
2370 def matchlines(body):
2370 def matchlines(body):
2371 begin = 0
2371 begin = 0
2372 linenum = 0
2372 linenum = 0
2373 while begin < len(body):
2373 while begin < len(body):
2374 match = regexp.search(body, begin)
2374 match = regexp.search(body, begin)
2375 if not match:
2375 if not match:
2376 break
2376 break
2377 mstart, mend = match.span()
2377 mstart, mend = match.span()
2378 linenum += body.count('\n', begin, mstart) + 1
2378 linenum += body.count('\n', begin, mstart) + 1
2379 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2379 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2380 begin = body.find('\n', mend) + 1 or len(body) + 1
2380 begin = body.find('\n', mend) + 1 or len(body) + 1
2381 lend = begin - 1
2381 lend = begin - 1
2382 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2382 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2383
2383
2384 class linestate(object):
2384 class linestate(object):
2385 def __init__(self, line, linenum, colstart, colend):
2385 def __init__(self, line, linenum, colstart, colend):
2386 self.line = line
2386 self.line = line
2387 self.linenum = linenum
2387 self.linenum = linenum
2388 self.colstart = colstart
2388 self.colstart = colstart
2389 self.colend = colend
2389 self.colend = colend
2390
2390
2391 def __hash__(self):
2391 def __hash__(self):
2392 return hash((self.linenum, self.line))
2392 return hash((self.linenum, self.line))
2393
2393
2394 def __eq__(self, other):
2394 def __eq__(self, other):
2395 return self.line == other.line
2395 return self.line == other.line
2396
2396
2397 def findpos(self):
2397 def findpos(self):
2398 """Iterate all (start, end) indices of matches"""
2398 """Iterate all (start, end) indices of matches"""
2399 yield self.colstart, self.colend
2399 yield self.colstart, self.colend
2400 p = self.colend
2400 p = self.colend
2401 while p < len(self.line):
2401 while p < len(self.line):
2402 m = regexp.search(self.line, p)
2402 m = regexp.search(self.line, p)
2403 if not m:
2403 if not m:
2404 break
2404 break
2405 yield m.span()
2405 yield m.span()
2406 p = m.end()
2406 p = m.end()
2407
2407
2408 matches = {}
2408 matches = {}
2409 copies = {}
2409 copies = {}
2410 def grepbody(fn, rev, body):
2410 def grepbody(fn, rev, body):
2411 matches[rev].setdefault(fn, [])
2411 matches[rev].setdefault(fn, [])
2412 m = matches[rev][fn]
2412 m = matches[rev][fn]
2413 for lnum, cstart, cend, line in matchlines(body):
2413 for lnum, cstart, cend, line in matchlines(body):
2414 s = linestate(line, lnum, cstart, cend)
2414 s = linestate(line, lnum, cstart, cend)
2415 m.append(s)
2415 m.append(s)
2416
2416
2417 def difflinestates(a, b):
2417 def difflinestates(a, b):
2418 sm = difflib.SequenceMatcher(None, a, b)
2418 sm = difflib.SequenceMatcher(None, a, b)
2419 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2419 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2420 if tag == 'insert':
2420 if tag == 'insert':
2421 for i in xrange(blo, bhi):
2421 for i in xrange(blo, bhi):
2422 yield ('+', b[i])
2422 yield ('+', b[i])
2423 elif tag == 'delete':
2423 elif tag == 'delete':
2424 for i in xrange(alo, ahi):
2424 for i in xrange(alo, ahi):
2425 yield ('-', a[i])
2425 yield ('-', a[i])
2426 elif tag == 'replace':
2426 elif tag == 'replace':
2427 for i in xrange(alo, ahi):
2427 for i in xrange(alo, ahi):
2428 yield ('-', a[i])
2428 yield ('-', a[i])
2429 for i in xrange(blo, bhi):
2429 for i in xrange(blo, bhi):
2430 yield ('+', b[i])
2430 yield ('+', b[i])
2431
2431
2432 def display(fm, fn, ctx, pstates, states):
2432 def display(fm, fn, ctx, pstates, states):
2433 rev = ctx.rev()
2433 rev = ctx.rev()
2434 if fm.isplain():
2434 if fm.isplain():
2435 formatuser = ui.shortuser
2435 formatuser = ui.shortuser
2436 else:
2436 else:
2437 formatuser = str
2437 formatuser = str
2438 if ui.quiet:
2438 if ui.quiet:
2439 datefmt = '%Y-%m-%d'
2439 datefmt = '%Y-%m-%d'
2440 else:
2440 else:
2441 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2441 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2442 found = False
2442 found = False
2443 @util.cachefunc
2443 @util.cachefunc
2444 def binary():
2444 def binary():
2445 flog = getfile(fn)
2445 flog = getfile(fn)
2446 return util.binary(flog.read(ctx.filenode(fn)))
2446 return util.binary(flog.read(ctx.filenode(fn)))
2447
2447
2448 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2448 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2449 if opts.get('all'):
2449 if opts.get('all'):
2450 iter = difflinestates(pstates, states)
2450 iter = difflinestates(pstates, states)
2451 else:
2451 else:
2452 iter = [('', l) for l in states]
2452 iter = [('', l) for l in states]
2453 for change, l in iter:
2453 for change, l in iter:
2454 fm.startitem()
2454 fm.startitem()
2455 fm.data(node=fm.hexfunc(ctx.node()))
2455 fm.data(node=fm.hexfunc(ctx.node()))
2456 cols = [
2456 cols = [
2457 ('filename', fn, True),
2457 ('filename', fn, True),
2458 ('rev', rev, True),
2458 ('rev', rev, True),
2459 ('linenumber', l.linenum, opts.get('line_number')),
2459 ('linenumber', l.linenum, opts.get('line_number')),
2460 ]
2460 ]
2461 if opts.get('all'):
2461 if opts.get('all'):
2462 cols.append(('change', change, True))
2462 cols.append(('change', change, True))
2463 cols.extend([
2463 cols.extend([
2464 ('user', formatuser(ctx.user()), opts.get('user')),
2464 ('user', formatuser(ctx.user()), opts.get('user')),
2465 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2465 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2466 ])
2466 ])
2467 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2467 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2468 for name, data, cond in cols:
2468 for name, data, cond in cols:
2469 field = fieldnamemap.get(name, name)
2469 field = fieldnamemap.get(name, name)
2470 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2470 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2471 if cond and name != lastcol:
2471 if cond and name != lastcol:
2472 fm.plain(sep, label='grep.sep')
2472 fm.plain(sep, label='grep.sep')
2473 if not opts.get('files_with_matches'):
2473 if not opts.get('files_with_matches'):
2474 fm.plain(sep, label='grep.sep')
2474 fm.plain(sep, label='grep.sep')
2475 if not opts.get('text') and binary():
2475 if not opts.get('text') and binary():
2476 fm.plain(_(" Binary file matches"))
2476 fm.plain(_(" Binary file matches"))
2477 else:
2477 else:
2478 displaymatches(fm.nested('texts'), l)
2478 displaymatches(fm.nested('texts'), l)
2479 fm.plain(eol)
2479 fm.plain(eol)
2480 found = True
2480 found = True
2481 if opts.get('files_with_matches'):
2481 if opts.get('files_with_matches'):
2482 break
2482 break
2483 return found
2483 return found
2484
2484
2485 def displaymatches(fm, l):
2485 def displaymatches(fm, l):
2486 p = 0
2486 p = 0
2487 for s, e in l.findpos():
2487 for s, e in l.findpos():
2488 if p < s:
2488 if p < s:
2489 fm.startitem()
2489 fm.startitem()
2490 fm.write('text', '%s', l.line[p:s])
2490 fm.write('text', '%s', l.line[p:s])
2491 fm.data(matched=False)
2491 fm.data(matched=False)
2492 fm.startitem()
2492 fm.startitem()
2493 fm.write('text', '%s', l.line[s:e], label='grep.match')
2493 fm.write('text', '%s', l.line[s:e], label='grep.match')
2494 fm.data(matched=True)
2494 fm.data(matched=True)
2495 p = e
2495 p = e
2496 if p < len(l.line):
2496 if p < len(l.line):
2497 fm.startitem()
2497 fm.startitem()
2498 fm.write('text', '%s', l.line[p:])
2498 fm.write('text', '%s', l.line[p:])
2499 fm.data(matched=False)
2499 fm.data(matched=False)
2500 fm.end()
2500 fm.end()
2501
2501
2502 skip = {}
2502 skip = {}
2503 revfiles = {}
2503 revfiles = {}
2504 match = scmutil.match(repo[None], pats, opts)
2504 match = scmutil.match(repo[None], pats, opts)
2505 found = False
2505 found = False
2506 follow = opts.get('follow')
2506 follow = opts.get('follow')
2507
2507
2508 def prep(ctx, fns):
2508 def prep(ctx, fns):
2509 rev = ctx.rev()
2509 rev = ctx.rev()
2510 pctx = ctx.p1()
2510 pctx = ctx.p1()
2511 parent = pctx.rev()
2511 parent = pctx.rev()
2512 matches.setdefault(rev, {})
2512 matches.setdefault(rev, {})
2513 matches.setdefault(parent, {})
2513 matches.setdefault(parent, {})
2514 files = revfiles.setdefault(rev, [])
2514 files = revfiles.setdefault(rev, [])
2515 for fn in fns:
2515 for fn in fns:
2516 flog = getfile(fn)
2516 flog = getfile(fn)
2517 try:
2517 try:
2518 fnode = ctx.filenode(fn)
2518 fnode = ctx.filenode(fn)
2519 except error.LookupError:
2519 except error.LookupError:
2520 continue
2520 continue
2521
2521
2522 copied = flog.renamed(fnode)
2522 copied = flog.renamed(fnode)
2523 copy = follow and copied and copied[0]
2523 copy = follow and copied and copied[0]
2524 if copy:
2524 if copy:
2525 copies.setdefault(rev, {})[fn] = copy
2525 copies.setdefault(rev, {})[fn] = copy
2526 if fn in skip:
2526 if fn in skip:
2527 if copy:
2527 if copy:
2528 skip[copy] = True
2528 skip[copy] = True
2529 continue
2529 continue
2530 files.append(fn)
2530 files.append(fn)
2531
2531
2532 if fn not in matches[rev]:
2532 if fn not in matches[rev]:
2533 grepbody(fn, rev, flog.read(fnode))
2533 grepbody(fn, rev, flog.read(fnode))
2534
2534
2535 pfn = copy or fn
2535 pfn = copy or fn
2536 if pfn not in matches[parent]:
2536 if pfn not in matches[parent]:
2537 try:
2537 try:
2538 fnode = pctx.filenode(pfn)
2538 fnode = pctx.filenode(pfn)
2539 grepbody(pfn, parent, flog.read(fnode))
2539 grepbody(pfn, parent, flog.read(fnode))
2540 except error.LookupError:
2540 except error.LookupError:
2541 pass
2541 pass
2542
2542
2543 ui.pager('grep')
2543 ui.pager('grep')
2544 fm = ui.formatter('grep', opts)
2544 fm = ui.formatter('grep', opts)
2545 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2545 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2546 rev = ctx.rev()
2546 rev = ctx.rev()
2547 parent = ctx.p1().rev()
2547 parent = ctx.p1().rev()
2548 for fn in sorted(revfiles.get(rev, [])):
2548 for fn in sorted(revfiles.get(rev, [])):
2549 states = matches[rev][fn]
2549 states = matches[rev][fn]
2550 copy = copies.get(rev, {}).get(fn)
2550 copy = copies.get(rev, {}).get(fn)
2551 if fn in skip:
2551 if fn in skip:
2552 if copy:
2552 if copy:
2553 skip[copy] = True
2553 skip[copy] = True
2554 continue
2554 continue
2555 pstates = matches.get(parent, {}).get(copy or fn, [])
2555 pstates = matches.get(parent, {}).get(copy or fn, [])
2556 if pstates or states:
2556 if pstates or states:
2557 r = display(fm, fn, ctx, pstates, states)
2557 r = display(fm, fn, ctx, pstates, states)
2558 found = found or r
2558 found = found or r
2559 if r and not opts.get('all'):
2559 if r and not opts.get('all'):
2560 skip[fn] = True
2560 skip[fn] = True
2561 if copy:
2561 if copy:
2562 skip[copy] = True
2562 skip[copy] = True
2563 del matches[rev]
2563 del matches[rev]
2564 del revfiles[rev]
2564 del revfiles[rev]
2565 fm.end()
2565 fm.end()
2566
2566
2567 return not found
2567 return not found
2568
2568
2569 @command('heads',
2569 @command('heads',
2570 [('r', 'rev', '',
2570 [('r', 'rev', '',
2571 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2571 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2572 ('t', 'topo', False, _('show topological heads only')),
2572 ('t', 'topo', False, _('show topological heads only')),
2573 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2573 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2574 ('c', 'closed', False, _('show normal and closed branch heads')),
2574 ('c', 'closed', False, _('show normal and closed branch heads')),
2575 ] + templateopts,
2575 ] + templateopts,
2576 _('[-ct] [-r STARTREV] [REV]...'), cmdtype=readonly)
2576 _('[-ct] [-r STARTREV] [REV]...'), cmdtype=readonly)
2577 def heads(ui, repo, *branchrevs, **opts):
2577 def heads(ui, repo, *branchrevs, **opts):
2578 """show branch heads
2578 """show branch heads
2579
2579
2580 With no arguments, show all open branch heads in the repository.
2580 With no arguments, show all open branch heads in the repository.
2581 Branch heads are changesets that have no descendants on the
2581 Branch heads are changesets that have no descendants on the
2582 same branch. They are where development generally takes place and
2582 same branch. They are where development generally takes place and
2583 are the usual targets for update and merge operations.
2583 are the usual targets for update and merge operations.
2584
2584
2585 If one or more REVs are given, only open branch heads on the
2585 If one or more REVs are given, only open branch heads on the
2586 branches associated with the specified changesets are shown. This
2586 branches associated with the specified changesets are shown. This
2587 means that you can use :hg:`heads .` to see the heads on the
2587 means that you can use :hg:`heads .` to see the heads on the
2588 currently checked-out branch.
2588 currently checked-out branch.
2589
2589
2590 If -c/--closed is specified, also show branch heads marked closed
2590 If -c/--closed is specified, also show branch heads marked closed
2591 (see :hg:`commit --close-branch`).
2591 (see :hg:`commit --close-branch`).
2592
2592
2593 If STARTREV is specified, only those heads that are descendants of
2593 If STARTREV is specified, only those heads that are descendants of
2594 STARTREV will be displayed.
2594 STARTREV will be displayed.
2595
2595
2596 If -t/--topo is specified, named branch mechanics will be ignored and only
2596 If -t/--topo is specified, named branch mechanics will be ignored and only
2597 topological heads (changesets with no children) will be shown.
2597 topological heads (changesets with no children) will be shown.
2598
2598
2599 Returns 0 if matching heads are found, 1 if not.
2599 Returns 0 if matching heads are found, 1 if not.
2600 """
2600 """
2601
2601
2602 opts = pycompat.byteskwargs(opts)
2602 opts = pycompat.byteskwargs(opts)
2603 start = None
2603 start = None
2604 rev = opts.get('rev')
2604 rev = opts.get('rev')
2605 if rev:
2605 if rev:
2606 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2606 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2607 start = scmutil.revsingle(repo, rev, None).node()
2607 start = scmutil.revsingle(repo, rev, None).node()
2608
2608
2609 if opts.get('topo'):
2609 if opts.get('topo'):
2610 heads = [repo[h] for h in repo.heads(start)]
2610 heads = [repo[h] for h in repo.heads(start)]
2611 else:
2611 else:
2612 heads = []
2612 heads = []
2613 for branch in repo.branchmap():
2613 for branch in repo.branchmap():
2614 heads += repo.branchheads(branch, start, opts.get('closed'))
2614 heads += repo.branchheads(branch, start, opts.get('closed'))
2615 heads = [repo[h] for h in heads]
2615 heads = [repo[h] for h in heads]
2616
2616
2617 if branchrevs:
2617 if branchrevs:
2618 branches = set(repo[br].branch() for br in branchrevs)
2618 branches = set(repo[br].branch() for br in branchrevs)
2619 heads = [h for h in heads if h.branch() in branches]
2619 heads = [h for h in heads if h.branch() in branches]
2620
2620
2621 if opts.get('active') and branchrevs:
2621 if opts.get('active') and branchrevs:
2622 dagheads = repo.heads(start)
2622 dagheads = repo.heads(start)
2623 heads = [h for h in heads if h.node() in dagheads]
2623 heads = [h for h in heads if h.node() in dagheads]
2624
2624
2625 if branchrevs:
2625 if branchrevs:
2626 haveheads = set(h.branch() for h in heads)
2626 haveheads = set(h.branch() for h in heads)
2627 if branches - haveheads:
2627 if branches - haveheads:
2628 headless = ', '.join(b for b in branches - haveheads)
2628 headless = ', '.join(b for b in branches - haveheads)
2629 msg = _('no open branch heads found on branches %s')
2629 msg = _('no open branch heads found on branches %s')
2630 if opts.get('rev'):
2630 if opts.get('rev'):
2631 msg += _(' (started at %s)') % opts['rev']
2631 msg += _(' (started at %s)') % opts['rev']
2632 ui.warn((msg + '\n') % headless)
2632 ui.warn((msg + '\n') % headless)
2633
2633
2634 if not heads:
2634 if not heads:
2635 return 1
2635 return 1
2636
2636
2637 ui.pager('heads')
2637 ui.pager('heads')
2638 heads = sorted(heads, key=lambda x: -x.rev())
2638 heads = sorted(heads, key=lambda x: -x.rev())
2639 displayer = cmdutil.show_changeset(ui, repo, opts)
2639 displayer = cmdutil.show_changeset(ui, repo, opts)
2640 for ctx in heads:
2640 for ctx in heads:
2641 displayer.show(ctx)
2641 displayer.show(ctx)
2642 displayer.close()
2642 displayer.close()
2643
2643
2644 @command('help',
2644 @command('help',
2645 [('e', 'extension', None, _('show only help for extensions')),
2645 [('e', 'extension', None, _('show only help for extensions')),
2646 ('c', 'command', None, _('show only help for commands')),
2646 ('c', 'command', None, _('show only help for commands')),
2647 ('k', 'keyword', None, _('show topics matching keyword')),
2647 ('k', 'keyword', None, _('show topics matching keyword')),
2648 ('s', 'system', [], _('show help for specific platform(s)')),
2648 ('s', 'system', [], _('show help for specific platform(s)')),
2649 ],
2649 ],
2650 _('[-ecks] [TOPIC]'),
2650 _('[-ecks] [TOPIC]'),
2651 norepo=True, cmdtype=readonly)
2651 norepo=True, cmdtype=readonly)
2652 def help_(ui, name=None, **opts):
2652 def help_(ui, name=None, **opts):
2653 """show help for a given topic or a help overview
2653 """show help for a given topic or a help overview
2654
2654
2655 With no arguments, print a list of commands with short help messages.
2655 With no arguments, print a list of commands with short help messages.
2656
2656
2657 Given a topic, extension, or command name, print help for that
2657 Given a topic, extension, or command name, print help for that
2658 topic.
2658 topic.
2659
2659
2660 Returns 0 if successful.
2660 Returns 0 if successful.
2661 """
2661 """
2662
2662
2663 keep = opts.get(r'system') or []
2663 keep = opts.get(r'system') or []
2664 if len(keep) == 0:
2664 if len(keep) == 0:
2665 if pycompat.sysplatform.startswith('win'):
2665 if pycompat.sysplatform.startswith('win'):
2666 keep.append('windows')
2666 keep.append('windows')
2667 elif pycompat.sysplatform == 'OpenVMS':
2667 elif pycompat.sysplatform == 'OpenVMS':
2668 keep.append('vms')
2668 keep.append('vms')
2669 elif pycompat.sysplatform == 'plan9':
2669 elif pycompat.sysplatform == 'plan9':
2670 keep.append('plan9')
2670 keep.append('plan9')
2671 else:
2671 else:
2672 keep.append('unix')
2672 keep.append('unix')
2673 keep.append(pycompat.sysplatform.lower())
2673 keep.append(pycompat.sysplatform.lower())
2674 if ui.verbose:
2674 if ui.verbose:
2675 keep.append('verbose')
2675 keep.append('verbose')
2676
2676
2677 commands = sys.modules[__name__]
2677 commands = sys.modules[__name__]
2678 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
2678 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
2679 ui.pager('help')
2679 ui.pager('help')
2680 ui.write(formatted)
2680 ui.write(formatted)
2681
2681
2682
2682
2683 @command('identify|id',
2683 @command('identify|id',
2684 [('r', 'rev', '',
2684 [('r', 'rev', '',
2685 _('identify the specified revision'), _('REV')),
2685 _('identify the specified revision'), _('REV')),
2686 ('n', 'num', None, _('show local revision number')),
2686 ('n', 'num', None, _('show local revision number')),
2687 ('i', 'id', None, _('show global revision id')),
2687 ('i', 'id', None, _('show global revision id')),
2688 ('b', 'branch', None, _('show branch')),
2688 ('b', 'branch', None, _('show branch')),
2689 ('t', 'tags', None, _('show tags')),
2689 ('t', 'tags', None, _('show tags')),
2690 ('B', 'bookmarks', None, _('show bookmarks')),
2690 ('B', 'bookmarks', None, _('show bookmarks')),
2691 ] + remoteopts + formatteropts,
2691 ] + remoteopts + formatteropts,
2692 _('[-nibtB] [-r REV] [SOURCE]'),
2692 _('[-nibtB] [-r REV] [SOURCE]'),
2693 optionalrepo=True, cmdtype=readonly)
2693 optionalrepo=True, cmdtype=readonly)
2694 def identify(ui, repo, source=None, rev=None,
2694 def identify(ui, repo, source=None, rev=None,
2695 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2695 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2696 """identify the working directory or specified revision
2696 """identify the working directory or specified revision
2697
2697
2698 Print a summary identifying the repository state at REV using one or
2698 Print a summary identifying the repository state at REV using one or
2699 two parent hash identifiers, followed by a "+" if the working
2699 two parent hash identifiers, followed by a "+" if the working
2700 directory has uncommitted changes, the branch name (if not default),
2700 directory has uncommitted changes, the branch name (if not default),
2701 a list of tags, and a list of bookmarks.
2701 a list of tags, and a list of bookmarks.
2702
2702
2703 When REV is not given, print a summary of the current state of the
2703 When REV is not given, print a summary of the current state of the
2704 repository.
2704 repository.
2705
2705
2706 Specifying a path to a repository root or Mercurial bundle will
2706 Specifying a path to a repository root or Mercurial bundle will
2707 cause lookup to operate on that repository/bundle.
2707 cause lookup to operate on that repository/bundle.
2708
2708
2709 .. container:: verbose
2709 .. container:: verbose
2710
2710
2711 Examples:
2711 Examples:
2712
2712
2713 - generate a build identifier for the working directory::
2713 - generate a build identifier for the working directory::
2714
2714
2715 hg id --id > build-id.dat
2715 hg id --id > build-id.dat
2716
2716
2717 - find the revision corresponding to a tag::
2717 - find the revision corresponding to a tag::
2718
2718
2719 hg id -n -r 1.3
2719 hg id -n -r 1.3
2720
2720
2721 - check the most recent revision of a remote repository::
2721 - check the most recent revision of a remote repository::
2722
2722
2723 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2723 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2724
2724
2725 See :hg:`log` for generating more information about specific revisions,
2725 See :hg:`log` for generating more information about specific revisions,
2726 including full hash identifiers.
2726 including full hash identifiers.
2727
2727
2728 Returns 0 if successful.
2728 Returns 0 if successful.
2729 """
2729 """
2730
2730
2731 opts = pycompat.byteskwargs(opts)
2731 opts = pycompat.byteskwargs(opts)
2732 if not repo and not source:
2732 if not repo and not source:
2733 raise error.Abort(_("there is no Mercurial repository here "
2733 raise error.Abort(_("there is no Mercurial repository here "
2734 "(.hg not found)"))
2734 "(.hg not found)"))
2735
2735
2736 if ui.debugflag:
2736 if ui.debugflag:
2737 hexfunc = hex
2737 hexfunc = hex
2738 else:
2738 else:
2739 hexfunc = short
2739 hexfunc = short
2740 default = not (num or id or branch or tags or bookmarks)
2740 default = not (num or id or branch or tags or bookmarks)
2741 output = []
2741 output = []
2742 revs = []
2742 revs = []
2743
2743
2744 if source:
2744 if source:
2745 source, branches = hg.parseurl(ui.expandpath(source))
2745 source, branches = hg.parseurl(ui.expandpath(source))
2746 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2746 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2747 repo = peer.local()
2747 repo = peer.local()
2748 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2748 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2749
2749
2750 fm = ui.formatter('identify', opts)
2750 fm = ui.formatter('identify', opts)
2751 fm.startitem()
2751 fm.startitem()
2752
2752
2753 if not repo:
2753 if not repo:
2754 if num or branch or tags:
2754 if num or branch or tags:
2755 raise error.Abort(
2755 raise error.Abort(
2756 _("can't query remote revision number, branch, or tags"))
2756 _("can't query remote revision number, branch, or tags"))
2757 if not rev and revs:
2757 if not rev and revs:
2758 rev = revs[0]
2758 rev = revs[0]
2759 if not rev:
2759 if not rev:
2760 rev = "tip"
2760 rev = "tip"
2761
2761
2762 remoterev = peer.lookup(rev)
2762 remoterev = peer.lookup(rev)
2763 hexrev = hexfunc(remoterev)
2763 hexrev = hexfunc(remoterev)
2764 if default or id:
2764 if default or id:
2765 output = [hexrev]
2765 output = [hexrev]
2766 fm.data(id=hexrev)
2766 fm.data(id=hexrev)
2767
2767
2768 def getbms():
2768 def getbms():
2769 bms = []
2769 bms = []
2770
2770
2771 if 'bookmarks' in peer.listkeys('namespaces'):
2771 if 'bookmarks' in peer.listkeys('namespaces'):
2772 hexremoterev = hex(remoterev)
2772 hexremoterev = hex(remoterev)
2773 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2773 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2774 if bmr == hexremoterev]
2774 if bmr == hexremoterev]
2775
2775
2776 return sorted(bms)
2776 return sorted(bms)
2777
2777
2778 bms = getbms()
2778 bms = getbms()
2779 if bookmarks:
2779 if bookmarks:
2780 output.extend(bms)
2780 output.extend(bms)
2781 elif default and not ui.quiet:
2781 elif default and not ui.quiet:
2782 # multiple bookmarks for a single parent separated by '/'
2782 # multiple bookmarks for a single parent separated by '/'
2783 bm = '/'.join(bms)
2783 bm = '/'.join(bms)
2784 if bm:
2784 if bm:
2785 output.append(bm)
2785 output.append(bm)
2786
2786
2787 fm.data(node=hex(remoterev))
2787 fm.data(node=hex(remoterev))
2788 fm.data(bookmarks=fm.formatlist(bms, name='bookmark'))
2788 fm.data(bookmarks=fm.formatlist(bms, name='bookmark'))
2789 else:
2789 else:
2790 if rev:
2790 if rev:
2791 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2791 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2792 ctx = scmutil.revsingle(repo, rev, None)
2792 ctx = scmutil.revsingle(repo, rev, None)
2793
2793
2794 if ctx.rev() is None:
2794 if ctx.rev() is None:
2795 ctx = repo[None]
2795 ctx = repo[None]
2796 parents = ctx.parents()
2796 parents = ctx.parents()
2797 taglist = []
2797 taglist = []
2798 for p in parents:
2798 for p in parents:
2799 taglist.extend(p.tags())
2799 taglist.extend(p.tags())
2800
2800
2801 dirty = ""
2801 dirty = ""
2802 if ctx.dirty(missing=True, merge=False, branch=False):
2802 if ctx.dirty(missing=True, merge=False, branch=False):
2803 dirty = '+'
2803 dirty = '+'
2804 fm.data(dirty=dirty)
2804 fm.data(dirty=dirty)
2805
2805
2806 hexoutput = [hexfunc(p.node()) for p in parents]
2806 hexoutput = [hexfunc(p.node()) for p in parents]
2807 if default or id:
2807 if default or id:
2808 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
2808 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
2809 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
2809 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
2810
2810
2811 if num:
2811 if num:
2812 numoutput = ["%d" % p.rev() for p in parents]
2812 numoutput = ["%d" % p.rev() for p in parents]
2813 output.append("%s%s" % ('+'.join(numoutput), dirty))
2813 output.append("%s%s" % ('+'.join(numoutput), dirty))
2814
2814
2815 fn = fm.nested('parents')
2815 fn = fm.nested('parents')
2816 for p in parents:
2816 for p in parents:
2817 fn.startitem()
2817 fn.startitem()
2818 fn.data(rev=p.rev())
2818 fn.data(rev=p.rev())
2819 fn.data(node=p.hex())
2819 fn.data(node=p.hex())
2820 fn.context(ctx=p)
2820 fn.context(ctx=p)
2821 fn.end()
2821 fn.end()
2822 else:
2822 else:
2823 hexoutput = hexfunc(ctx.node())
2823 hexoutput = hexfunc(ctx.node())
2824 if default or id:
2824 if default or id:
2825 output = [hexoutput]
2825 output = [hexoutput]
2826 fm.data(id=hexoutput)
2826 fm.data(id=hexoutput)
2827
2827
2828 if num:
2828 if num:
2829 output.append(pycompat.bytestr(ctx.rev()))
2829 output.append(pycompat.bytestr(ctx.rev()))
2830 taglist = ctx.tags()
2830 taglist = ctx.tags()
2831
2831
2832 if default and not ui.quiet:
2832 if default and not ui.quiet:
2833 b = ctx.branch()
2833 b = ctx.branch()
2834 if b != 'default':
2834 if b != 'default':
2835 output.append("(%s)" % b)
2835 output.append("(%s)" % b)
2836
2836
2837 # multiple tags for a single parent separated by '/'
2837 # multiple tags for a single parent separated by '/'
2838 t = '/'.join(taglist)
2838 t = '/'.join(taglist)
2839 if t:
2839 if t:
2840 output.append(t)
2840 output.append(t)
2841
2841
2842 # multiple bookmarks for a single parent separated by '/'
2842 # multiple bookmarks for a single parent separated by '/'
2843 bm = '/'.join(ctx.bookmarks())
2843 bm = '/'.join(ctx.bookmarks())
2844 if bm:
2844 if bm:
2845 output.append(bm)
2845 output.append(bm)
2846 else:
2846 else:
2847 if branch:
2847 if branch:
2848 output.append(ctx.branch())
2848 output.append(ctx.branch())
2849
2849
2850 if tags:
2850 if tags:
2851 output.extend(taglist)
2851 output.extend(taglist)
2852
2852
2853 if bookmarks:
2853 if bookmarks:
2854 output.extend(ctx.bookmarks())
2854 output.extend(ctx.bookmarks())
2855
2855
2856 fm.data(node=ctx.hex())
2856 fm.data(node=ctx.hex())
2857 fm.data(branch=ctx.branch())
2857 fm.data(branch=ctx.branch())
2858 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
2858 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
2859 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
2859 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
2860 fm.context(ctx=ctx)
2860 fm.context(ctx=ctx)
2861
2861
2862 fm.plain("%s\n" % ' '.join(output))
2862 fm.plain("%s\n" % ' '.join(output))
2863 fm.end()
2863 fm.end()
2864
2864
2865 @command('import|patch',
2865 @command('import|patch',
2866 [('p', 'strip', 1,
2866 [('p', 'strip', 1,
2867 _('directory strip option for patch. This has the same '
2867 _('directory strip option for patch. This has the same '
2868 'meaning as the corresponding patch option'), _('NUM')),
2868 'meaning as the corresponding patch option'), _('NUM')),
2869 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
2869 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
2870 ('e', 'edit', False, _('invoke editor on commit messages')),
2870 ('e', 'edit', False, _('invoke editor on commit messages')),
2871 ('f', 'force', None,
2871 ('f', 'force', None,
2872 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
2872 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
2873 ('', 'no-commit', None,
2873 ('', 'no-commit', None,
2874 _("don't commit, just update the working directory")),
2874 _("don't commit, just update the working directory")),
2875 ('', 'bypass', None,
2875 ('', 'bypass', None,
2876 _("apply patch without touching the working directory")),
2876 _("apply patch without touching the working directory")),
2877 ('', 'partial', None,
2877 ('', 'partial', None,
2878 _('commit even if some hunks fail')),
2878 _('commit even if some hunks fail')),
2879 ('', 'exact', None,
2879 ('', 'exact', None,
2880 _('abort if patch would apply lossily')),
2880 _('abort if patch would apply lossily')),
2881 ('', 'prefix', '',
2881 ('', 'prefix', '',
2882 _('apply patch to subdirectory'), _('DIR')),
2882 _('apply patch to subdirectory'), _('DIR')),
2883 ('', 'import-branch', None,
2883 ('', 'import-branch', None,
2884 _('use any branch information in patch (implied by --exact)'))] +
2884 _('use any branch information in patch (implied by --exact)'))] +
2885 commitopts + commitopts2 + similarityopts,
2885 commitopts + commitopts2 + similarityopts,
2886 _('[OPTION]... PATCH...'))
2886 _('[OPTION]... PATCH...'))
2887 def import_(ui, repo, patch1=None, *patches, **opts):
2887 def import_(ui, repo, patch1=None, *patches, **opts):
2888 """import an ordered set of patches
2888 """import an ordered set of patches
2889
2889
2890 Import a list of patches and commit them individually (unless
2890 Import a list of patches and commit them individually (unless
2891 --no-commit is specified).
2891 --no-commit is specified).
2892
2892
2893 To read a patch from standard input (stdin), use "-" as the patch
2893 To read a patch from standard input (stdin), use "-" as the patch
2894 name. If a URL is specified, the patch will be downloaded from
2894 name. If a URL is specified, the patch will be downloaded from
2895 there.
2895 there.
2896
2896
2897 Import first applies changes to the working directory (unless
2897 Import first applies changes to the working directory (unless
2898 --bypass is specified), import will abort if there are outstanding
2898 --bypass is specified), import will abort if there are outstanding
2899 changes.
2899 changes.
2900
2900
2901 Use --bypass to apply and commit patches directly to the
2901 Use --bypass to apply and commit patches directly to the
2902 repository, without affecting the working directory. Without
2902 repository, without affecting the working directory. Without
2903 --exact, patches will be applied on top of the working directory
2903 --exact, patches will be applied on top of the working directory
2904 parent revision.
2904 parent revision.
2905
2905
2906 You can import a patch straight from a mail message. Even patches
2906 You can import a patch straight from a mail message. Even patches
2907 as attachments work (to use the body part, it must have type
2907 as attachments work (to use the body part, it must have type
2908 text/plain or text/x-patch). From and Subject headers of email
2908 text/plain or text/x-patch). From and Subject headers of email
2909 message are used as default committer and commit message. All
2909 message are used as default committer and commit message. All
2910 text/plain body parts before first diff are added to the commit
2910 text/plain body parts before first diff are added to the commit
2911 message.
2911 message.
2912
2912
2913 If the imported patch was generated by :hg:`export`, user and
2913 If the imported patch was generated by :hg:`export`, user and
2914 description from patch override values from message headers and
2914 description from patch override values from message headers and
2915 body. Values given on command line with -m/--message and -u/--user
2915 body. Values given on command line with -m/--message and -u/--user
2916 override these.
2916 override these.
2917
2917
2918 If --exact is specified, import will set the working directory to
2918 If --exact is specified, import will set the working directory to
2919 the parent of each patch before applying it, and will abort if the
2919 the parent of each patch before applying it, and will abort if the
2920 resulting changeset has a different ID than the one recorded in
2920 resulting changeset has a different ID than the one recorded in
2921 the patch. This will guard against various ways that portable
2921 the patch. This will guard against various ways that portable
2922 patch formats and mail systems might fail to transfer Mercurial
2922 patch formats and mail systems might fail to transfer Mercurial
2923 data or metadata. See :hg:`bundle` for lossless transmission.
2923 data or metadata. See :hg:`bundle` for lossless transmission.
2924
2924
2925 Use --partial to ensure a changeset will be created from the patch
2925 Use --partial to ensure a changeset will be created from the patch
2926 even if some hunks fail to apply. Hunks that fail to apply will be
2926 even if some hunks fail to apply. Hunks that fail to apply will be
2927 written to a <target-file>.rej file. Conflicts can then be resolved
2927 written to a <target-file>.rej file. Conflicts can then be resolved
2928 by hand before :hg:`commit --amend` is run to update the created
2928 by hand before :hg:`commit --amend` is run to update the created
2929 changeset. This flag exists to let people import patches that
2929 changeset. This flag exists to let people import patches that
2930 partially apply without losing the associated metadata (author,
2930 partially apply without losing the associated metadata (author,
2931 date, description, ...).
2931 date, description, ...).
2932
2932
2933 .. note::
2933 .. note::
2934
2934
2935 When no hunks apply cleanly, :hg:`import --partial` will create
2935 When no hunks apply cleanly, :hg:`import --partial` will create
2936 an empty changeset, importing only the patch metadata.
2936 an empty changeset, importing only the patch metadata.
2937
2937
2938 With -s/--similarity, hg will attempt to discover renames and
2938 With -s/--similarity, hg will attempt to discover renames and
2939 copies in the patch in the same way as :hg:`addremove`.
2939 copies in the patch in the same way as :hg:`addremove`.
2940
2940
2941 It is possible to use external patch programs to perform the patch
2941 It is possible to use external patch programs to perform the patch
2942 by setting the ``ui.patch`` configuration option. For the default
2942 by setting the ``ui.patch`` configuration option. For the default
2943 internal tool, the fuzz can also be configured via ``patch.fuzz``.
2943 internal tool, the fuzz can also be configured via ``patch.fuzz``.
2944 See :hg:`help config` for more information about configuration
2944 See :hg:`help config` for more information about configuration
2945 files and how to use these options.
2945 files and how to use these options.
2946
2946
2947 See :hg:`help dates` for a list of formats valid for -d/--date.
2947 See :hg:`help dates` for a list of formats valid for -d/--date.
2948
2948
2949 .. container:: verbose
2949 .. container:: verbose
2950
2950
2951 Examples:
2951 Examples:
2952
2952
2953 - import a traditional patch from a website and detect renames::
2953 - import a traditional patch from a website and detect renames::
2954
2954
2955 hg import -s 80 http://example.com/bugfix.patch
2955 hg import -s 80 http://example.com/bugfix.patch
2956
2956
2957 - import a changeset from an hgweb server::
2957 - import a changeset from an hgweb server::
2958
2958
2959 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
2959 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
2960
2960
2961 - import all the patches in an Unix-style mbox::
2961 - import all the patches in an Unix-style mbox::
2962
2962
2963 hg import incoming-patches.mbox
2963 hg import incoming-patches.mbox
2964
2964
2965 - import patches from stdin::
2965 - import patches from stdin::
2966
2966
2967 hg import -
2967 hg import -
2968
2968
2969 - attempt to exactly restore an exported changeset (not always
2969 - attempt to exactly restore an exported changeset (not always
2970 possible)::
2970 possible)::
2971
2971
2972 hg import --exact proposed-fix.patch
2972 hg import --exact proposed-fix.patch
2973
2973
2974 - use an external tool to apply a patch which is too fuzzy for
2974 - use an external tool to apply a patch which is too fuzzy for
2975 the default internal tool.
2975 the default internal tool.
2976
2976
2977 hg import --config ui.patch="patch --merge" fuzzy.patch
2977 hg import --config ui.patch="patch --merge" fuzzy.patch
2978
2978
2979 - change the default fuzzing from 2 to a less strict 7
2979 - change the default fuzzing from 2 to a less strict 7
2980
2980
2981 hg import --config ui.fuzz=7 fuzz.patch
2981 hg import --config ui.fuzz=7 fuzz.patch
2982
2982
2983 Returns 0 on success, 1 on partial success (see --partial).
2983 Returns 0 on success, 1 on partial success (see --partial).
2984 """
2984 """
2985
2985
2986 opts = pycompat.byteskwargs(opts)
2986 opts = pycompat.byteskwargs(opts)
2987 if not patch1:
2987 if not patch1:
2988 raise error.Abort(_('need at least one patch to import'))
2988 raise error.Abort(_('need at least one patch to import'))
2989
2989
2990 patches = (patch1,) + patches
2990 patches = (patch1,) + patches
2991
2991
2992 date = opts.get('date')
2992 date = opts.get('date')
2993 if date:
2993 if date:
2994 opts['date'] = util.parsedate(date)
2994 opts['date'] = util.parsedate(date)
2995
2995
2996 exact = opts.get('exact')
2996 exact = opts.get('exact')
2997 update = not opts.get('bypass')
2997 update = not opts.get('bypass')
2998 if not update and opts.get('no_commit'):
2998 if not update and opts.get('no_commit'):
2999 raise error.Abort(_('cannot use --no-commit with --bypass'))
2999 raise error.Abort(_('cannot use --no-commit with --bypass'))
3000 try:
3000 try:
3001 sim = float(opts.get('similarity') or 0)
3001 sim = float(opts.get('similarity') or 0)
3002 except ValueError:
3002 except ValueError:
3003 raise error.Abort(_('similarity must be a number'))
3003 raise error.Abort(_('similarity must be a number'))
3004 if sim < 0 or sim > 100:
3004 if sim < 0 or sim > 100:
3005 raise error.Abort(_('similarity must be between 0 and 100'))
3005 raise error.Abort(_('similarity must be between 0 and 100'))
3006 if sim and not update:
3006 if sim and not update:
3007 raise error.Abort(_('cannot use --similarity with --bypass'))
3007 raise error.Abort(_('cannot use --similarity with --bypass'))
3008 if exact:
3008 if exact:
3009 if opts.get('edit'):
3009 if opts.get('edit'):
3010 raise error.Abort(_('cannot use --exact with --edit'))
3010 raise error.Abort(_('cannot use --exact with --edit'))
3011 if opts.get('prefix'):
3011 if opts.get('prefix'):
3012 raise error.Abort(_('cannot use --exact with --prefix'))
3012 raise error.Abort(_('cannot use --exact with --prefix'))
3013
3013
3014 base = opts["base"]
3014 base = opts["base"]
3015 wlock = dsguard = lock = tr = None
3015 wlock = dsguard = lock = tr = None
3016 msgs = []
3016 msgs = []
3017 ret = 0
3017 ret = 0
3018
3018
3019
3019
3020 try:
3020 try:
3021 wlock = repo.wlock()
3021 wlock = repo.wlock()
3022
3022
3023 if update:
3023 if update:
3024 cmdutil.checkunfinished(repo)
3024 cmdutil.checkunfinished(repo)
3025 if (exact or not opts.get('force')):
3025 if (exact or not opts.get('force')):
3026 cmdutil.bailifchanged(repo)
3026 cmdutil.bailifchanged(repo)
3027
3027
3028 if not opts.get('no_commit'):
3028 if not opts.get('no_commit'):
3029 lock = repo.lock()
3029 lock = repo.lock()
3030 tr = repo.transaction('import')
3030 tr = repo.transaction('import')
3031 else:
3031 else:
3032 dsguard = dirstateguard.dirstateguard(repo, 'import')
3032 dsguard = dirstateguard.dirstateguard(repo, 'import')
3033 parents = repo[None].parents()
3033 parents = repo[None].parents()
3034 for patchurl in patches:
3034 for patchurl in patches:
3035 if patchurl == '-':
3035 if patchurl == '-':
3036 ui.status(_('applying patch from stdin\n'))
3036 ui.status(_('applying patch from stdin\n'))
3037 patchfile = ui.fin
3037 patchfile = ui.fin
3038 patchurl = 'stdin' # for error message
3038 patchurl = 'stdin' # for error message
3039 else:
3039 else:
3040 patchurl = os.path.join(base, patchurl)
3040 patchurl = os.path.join(base, patchurl)
3041 ui.status(_('applying %s\n') % patchurl)
3041 ui.status(_('applying %s\n') % patchurl)
3042 patchfile = hg.openpath(ui, patchurl)
3042 patchfile = hg.openpath(ui, patchurl)
3043
3043
3044 haspatch = False
3044 haspatch = False
3045 for hunk in patch.split(patchfile):
3045 for hunk in patch.split(patchfile):
3046 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
3046 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
3047 parents, opts,
3047 parents, opts,
3048 msgs, hg.clean)
3048 msgs, hg.clean)
3049 if msg:
3049 if msg:
3050 haspatch = True
3050 haspatch = True
3051 ui.note(msg + '\n')
3051 ui.note(msg + '\n')
3052 if update or exact:
3052 if update or exact:
3053 parents = repo[None].parents()
3053 parents = repo[None].parents()
3054 else:
3054 else:
3055 parents = [repo[node]]
3055 parents = [repo[node]]
3056 if rej:
3056 if rej:
3057 ui.write_err(_("patch applied partially\n"))
3057 ui.write_err(_("patch applied partially\n"))
3058 ui.write_err(_("(fix the .rej files and run "
3058 ui.write_err(_("(fix the .rej files and run "
3059 "`hg commit --amend`)\n"))
3059 "`hg commit --amend`)\n"))
3060 ret = 1
3060 ret = 1
3061 break
3061 break
3062
3062
3063 if not haspatch:
3063 if not haspatch:
3064 raise error.Abort(_('%s: no diffs found') % patchurl)
3064 raise error.Abort(_('%s: no diffs found') % patchurl)
3065
3065
3066 if tr:
3066 if tr:
3067 tr.close()
3067 tr.close()
3068 if msgs:
3068 if msgs:
3069 repo.savecommitmessage('\n* * *\n'.join(msgs))
3069 repo.savecommitmessage('\n* * *\n'.join(msgs))
3070 if dsguard:
3070 if dsguard:
3071 dsguard.close()
3071 dsguard.close()
3072 return ret
3072 return ret
3073 finally:
3073 finally:
3074 if tr:
3074 if tr:
3075 tr.release()
3075 tr.release()
3076 release(lock, dsguard, wlock)
3076 release(lock, dsguard, wlock)
3077
3077
3078 @command('incoming|in',
3078 @command('incoming|in',
3079 [('f', 'force', None,
3079 [('f', 'force', None,
3080 _('run even if remote repository is unrelated')),
3080 _('run even if remote repository is unrelated')),
3081 ('n', 'newest-first', None, _('show newest record first')),
3081 ('n', 'newest-first', None, _('show newest record first')),
3082 ('', 'bundle', '',
3082 ('', 'bundle', '',
3083 _('file to store the bundles into'), _('FILE')),
3083 _('file to store the bundles into'), _('FILE')),
3084 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3084 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3085 ('B', 'bookmarks', False, _("compare bookmarks")),
3085 ('B', 'bookmarks', False, _("compare bookmarks")),
3086 ('b', 'branch', [],
3086 ('b', 'branch', [],
3087 _('a specific branch you would like to pull'), _('BRANCH')),
3087 _('a specific branch you would like to pull'), _('BRANCH')),
3088 ] + logopts + remoteopts + subrepoopts,
3088 ] + logopts + remoteopts + subrepoopts,
3089 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3089 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3090 def incoming(ui, repo, source="default", **opts):
3090 def incoming(ui, repo, source="default", **opts):
3091 """show new changesets found in source
3091 """show new changesets found in source
3092
3092
3093 Show new changesets found in the specified path/URL or the default
3093 Show new changesets found in the specified path/URL or the default
3094 pull location. These are the changesets that would have been pulled
3094 pull location. These are the changesets that would have been pulled
3095 by :hg:`pull` at the time you issued this command.
3095 by :hg:`pull` at the time you issued this command.
3096
3096
3097 See pull for valid source format details.
3097 See pull for valid source format details.
3098
3098
3099 .. container:: verbose
3099 .. container:: verbose
3100
3100
3101 With -B/--bookmarks, the result of bookmark comparison between
3101 With -B/--bookmarks, the result of bookmark comparison between
3102 local and remote repositories is displayed. With -v/--verbose,
3102 local and remote repositories is displayed. With -v/--verbose,
3103 status is also displayed for each bookmark like below::
3103 status is also displayed for each bookmark like below::
3104
3104
3105 BM1 01234567890a added
3105 BM1 01234567890a added
3106 BM2 1234567890ab advanced
3106 BM2 1234567890ab advanced
3107 BM3 234567890abc diverged
3107 BM3 234567890abc diverged
3108 BM4 34567890abcd changed
3108 BM4 34567890abcd changed
3109
3109
3110 The action taken locally when pulling depends on the
3110 The action taken locally when pulling depends on the
3111 status of each bookmark:
3111 status of each bookmark:
3112
3112
3113 :``added``: pull will create it
3113 :``added``: pull will create it
3114 :``advanced``: pull will update it
3114 :``advanced``: pull will update it
3115 :``diverged``: pull will create a divergent bookmark
3115 :``diverged``: pull will create a divergent bookmark
3116 :``changed``: result depends on remote changesets
3116 :``changed``: result depends on remote changesets
3117
3117
3118 From the point of view of pulling behavior, bookmark
3118 From the point of view of pulling behavior, bookmark
3119 existing only in the remote repository are treated as ``added``,
3119 existing only in the remote repository are treated as ``added``,
3120 even if it is in fact locally deleted.
3120 even if it is in fact locally deleted.
3121
3121
3122 .. container:: verbose
3122 .. container:: verbose
3123
3123
3124 For remote repository, using --bundle avoids downloading the
3124 For remote repository, using --bundle avoids downloading the
3125 changesets twice if the incoming is followed by a pull.
3125 changesets twice if the incoming is followed by a pull.
3126
3126
3127 Examples:
3127 Examples:
3128
3128
3129 - show incoming changes with patches and full description::
3129 - show incoming changes with patches and full description::
3130
3130
3131 hg incoming -vp
3131 hg incoming -vp
3132
3132
3133 - show incoming changes excluding merges, store a bundle::
3133 - show incoming changes excluding merges, store a bundle::
3134
3134
3135 hg in -vpM --bundle incoming.hg
3135 hg in -vpM --bundle incoming.hg
3136 hg pull incoming.hg
3136 hg pull incoming.hg
3137
3137
3138 - briefly list changes inside a bundle::
3138 - briefly list changes inside a bundle::
3139
3139
3140 hg in changes.hg -T "{desc|firstline}\\n"
3140 hg in changes.hg -T "{desc|firstline}\\n"
3141
3141
3142 Returns 0 if there are incoming changes, 1 otherwise.
3142 Returns 0 if there are incoming changes, 1 otherwise.
3143 """
3143 """
3144 opts = pycompat.byteskwargs(opts)
3144 opts = pycompat.byteskwargs(opts)
3145 if opts.get('graph'):
3145 if opts.get('graph'):
3146 cmdutil.checkunsupportedgraphflags([], opts)
3146 cmdutil.checkunsupportedgraphflags([], opts)
3147 def display(other, chlist, displayer):
3147 def display(other, chlist, displayer):
3148 revdag = cmdutil.graphrevs(other, chlist, opts)
3148 revdag = cmdutil.graphrevs(other, chlist, opts)
3149 cmdutil.displaygraph(ui, repo, revdag, displayer,
3149 cmdutil.displaygraph(ui, repo, revdag, displayer,
3150 graphmod.asciiedges)
3150 graphmod.asciiedges)
3151
3151
3152 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3152 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3153 return 0
3153 return 0
3154
3154
3155 if opts.get('bundle') and opts.get('subrepos'):
3155 if opts.get('bundle') and opts.get('subrepos'):
3156 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3156 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3157
3157
3158 if opts.get('bookmarks'):
3158 if opts.get('bookmarks'):
3159 source, branches = hg.parseurl(ui.expandpath(source),
3159 source, branches = hg.parseurl(ui.expandpath(source),
3160 opts.get('branch'))
3160 opts.get('branch'))
3161 other = hg.peer(repo, opts, source)
3161 other = hg.peer(repo, opts, source)
3162 if 'bookmarks' not in other.listkeys('namespaces'):
3162 if 'bookmarks' not in other.listkeys('namespaces'):
3163 ui.warn(_("remote doesn't support bookmarks\n"))
3163 ui.warn(_("remote doesn't support bookmarks\n"))
3164 return 0
3164 return 0
3165 ui.pager('incoming')
3165 ui.pager('incoming')
3166 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3166 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3167 return bookmarks.incoming(ui, repo, other)
3167 return bookmarks.incoming(ui, repo, other)
3168
3168
3169 repo._subtoppath = ui.expandpath(source)
3169 repo._subtoppath = ui.expandpath(source)
3170 try:
3170 try:
3171 return hg.incoming(ui, repo, source, opts)
3171 return hg.incoming(ui, repo, source, opts)
3172 finally:
3172 finally:
3173 del repo._subtoppath
3173 del repo._subtoppath
3174
3174
3175
3175
3176 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3176 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3177 norepo=True)
3177 norepo=True)
3178 def init(ui, dest=".", **opts):
3178 def init(ui, dest=".", **opts):
3179 """create a new repository in the given directory
3179 """create a new repository in the given directory
3180
3180
3181 Initialize a new repository in the given directory. If the given
3181 Initialize a new repository in the given directory. If the given
3182 directory does not exist, it will be created.
3182 directory does not exist, it will be created.
3183
3183
3184 If no directory is given, the current directory is used.
3184 If no directory is given, the current directory is used.
3185
3185
3186 It is possible to specify an ``ssh://`` URL as the destination.
3186 It is possible to specify an ``ssh://`` URL as the destination.
3187 See :hg:`help urls` for more information.
3187 See :hg:`help urls` for more information.
3188
3188
3189 Returns 0 on success.
3189 Returns 0 on success.
3190 """
3190 """
3191 opts = pycompat.byteskwargs(opts)
3191 opts = pycompat.byteskwargs(opts)
3192 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3192 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3193
3193
3194 @command('locate',
3194 @command('locate',
3195 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3195 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3196 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3196 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3197 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3197 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3198 ] + walkopts,
3198 ] + walkopts,
3199 _('[OPTION]... [PATTERN]...'))
3199 _('[OPTION]... [PATTERN]...'))
3200 def locate(ui, repo, *pats, **opts):
3200 def locate(ui, repo, *pats, **opts):
3201 """locate files matching specific patterns (DEPRECATED)
3201 """locate files matching specific patterns (DEPRECATED)
3202
3202
3203 Print files under Mercurial control in the working directory whose
3203 Print files under Mercurial control in the working directory whose
3204 names match the given patterns.
3204 names match the given patterns.
3205
3205
3206 By default, this command searches all directories in the working
3206 By default, this command searches all directories in the working
3207 directory. To search just the current directory and its
3207 directory. To search just the current directory and its
3208 subdirectories, use "--include .".
3208 subdirectories, use "--include .".
3209
3209
3210 If no patterns are given to match, this command prints the names
3210 If no patterns are given to match, this command prints the names
3211 of all files under Mercurial control in the working directory.
3211 of all files under Mercurial control in the working directory.
3212
3212
3213 If you want to feed the output of this command into the "xargs"
3213 If you want to feed the output of this command into the "xargs"
3214 command, use the -0 option to both this command and "xargs". This
3214 command, use the -0 option to both this command and "xargs". This
3215 will avoid the problem of "xargs" treating single filenames that
3215 will avoid the problem of "xargs" treating single filenames that
3216 contain whitespace as multiple filenames.
3216 contain whitespace as multiple filenames.
3217
3217
3218 See :hg:`help files` for a more versatile command.
3218 See :hg:`help files` for a more versatile command.
3219
3219
3220 Returns 0 if a match is found, 1 otherwise.
3220 Returns 0 if a match is found, 1 otherwise.
3221 """
3221 """
3222 opts = pycompat.byteskwargs(opts)
3222 opts = pycompat.byteskwargs(opts)
3223 if opts.get('print0'):
3223 if opts.get('print0'):
3224 end = '\0'
3224 end = '\0'
3225 else:
3225 else:
3226 end = '\n'
3226 end = '\n'
3227 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3227 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3228
3228
3229 ret = 1
3229 ret = 1
3230 ctx = repo[rev]
3230 ctx = repo[rev]
3231 m = scmutil.match(ctx, pats, opts, default='relglob',
3231 m = scmutil.match(ctx, pats, opts, default='relglob',
3232 badfn=lambda x, y: False)
3232 badfn=lambda x, y: False)
3233
3233
3234 ui.pager('locate')
3234 ui.pager('locate')
3235 for abs in ctx.matches(m):
3235 for abs in ctx.matches(m):
3236 if opts.get('fullpath'):
3236 if opts.get('fullpath'):
3237 ui.write(repo.wjoin(abs), end)
3237 ui.write(repo.wjoin(abs), end)
3238 else:
3238 else:
3239 ui.write(((pats and m.rel(abs)) or abs), end)
3239 ui.write(((pats and m.rel(abs)) or abs), end)
3240 ret = 0
3240 ret = 0
3241
3241
3242 return ret
3242 return ret
3243
3243
3244 @command('^log|history',
3244 @command('^log|history',
3245 [('f', 'follow', None,
3245 [('f', 'follow', None,
3246 _('follow changeset history, or file history across copies and renames')),
3246 _('follow changeset history, or file history across copies and renames')),
3247 ('', 'follow-first', None,
3247 ('', 'follow-first', None,
3248 _('only follow the first parent of merge changesets (DEPRECATED)')),
3248 _('only follow the first parent of merge changesets (DEPRECATED)')),
3249 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3249 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3250 ('C', 'copies', None, _('show copied files')),
3250 ('C', 'copies', None, _('show copied files')),
3251 ('k', 'keyword', [],
3251 ('k', 'keyword', [],
3252 _('do case-insensitive search for a given text'), _('TEXT')),
3252 _('do case-insensitive search for a given text'), _('TEXT')),
3253 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3253 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3254 ('L', 'line-range', [],
3254 ('L', 'line-range', [],
3255 _('follow line range of specified file (EXPERIMENTAL)'),
3255 _('follow line range of specified file (EXPERIMENTAL)'),
3256 _('FILE,RANGE')),
3256 _('FILE,RANGE')),
3257 ('', 'removed', None, _('include revisions where files were removed')),
3257 ('', 'removed', None, _('include revisions where files were removed')),
3258 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3258 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3259 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3259 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3260 ('', 'only-branch', [],
3260 ('', 'only-branch', [],
3261 _('show only changesets within the given named branch (DEPRECATED)'),
3261 _('show only changesets within the given named branch (DEPRECATED)'),
3262 _('BRANCH')),
3262 _('BRANCH')),
3263 ('b', 'branch', [],
3263 ('b', 'branch', [],
3264 _('show changesets within the given named branch'), _('BRANCH')),
3264 _('show changesets within the given named branch'), _('BRANCH')),
3265 ('P', 'prune', [],
3265 ('P', 'prune', [],
3266 _('do not display revision or any of its ancestors'), _('REV')),
3266 _('do not display revision or any of its ancestors'), _('REV')),
3267 ] + logopts + walkopts,
3267 ] + logopts + walkopts,
3268 _('[OPTION]... [FILE]'),
3268 _('[OPTION]... [FILE]'),
3269 inferrepo=True, cmdtype=readonly)
3269 inferrepo=True, cmdtype=readonly)
3270 def log(ui, repo, *pats, **opts):
3270 def log(ui, repo, *pats, **opts):
3271 """show revision history of entire repository or files
3271 """show revision history of entire repository or files
3272
3272
3273 Print the revision history of the specified files or the entire
3273 Print the revision history of the specified files or the entire
3274 project.
3274 project.
3275
3275
3276 If no revision range is specified, the default is ``tip:0`` unless
3276 If no revision range is specified, the default is ``tip:0`` unless
3277 --follow is set, in which case the working directory parent is
3277 --follow is set, in which case the working directory parent is
3278 used as the starting revision.
3278 used as the starting revision.
3279
3279
3280 File history is shown without following rename or copy history of
3280 File history is shown without following rename or copy history of
3281 files. Use -f/--follow with a filename to follow history across
3281 files. Use -f/--follow with a filename to follow history across
3282 renames and copies. --follow without a filename will only show
3282 renames and copies. --follow without a filename will only show
3283 ancestors or descendants of the starting revision.
3283 ancestors or descendants of the starting revision.
3284
3284
3285 By default this command prints revision number and changeset id,
3285 By default this command prints revision number and changeset id,
3286 tags, non-trivial parents, user, date and time, and a summary for
3286 tags, non-trivial parents, user, date and time, and a summary for
3287 each commit. When the -v/--verbose switch is used, the list of
3287 each commit. When the -v/--verbose switch is used, the list of
3288 changed files and full commit message are shown.
3288 changed files and full commit message are shown.
3289
3289
3290 With --graph the revisions are shown as an ASCII art DAG with the most
3290 With --graph the revisions are shown as an ASCII art DAG with the most
3291 recent changeset at the top.
3291 recent changeset at the top.
3292 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
3292 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
3293 and '+' represents a fork where the changeset from the lines below is a
3293 and '+' represents a fork where the changeset from the lines below is a
3294 parent of the 'o' merge on the same line.
3294 parent of the 'o' merge on the same line.
3295 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3295 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3296 of a '|' indicates one or more revisions in a path are omitted.
3296 of a '|' indicates one or more revisions in a path are omitted.
3297
3297
3298 .. container:: verbose
3298 .. container:: verbose
3299
3299
3300 Use -L/--line-range FILE,M:N options to follow the history of lines
3300 Use -L/--line-range FILE,M:N options to follow the history of lines
3301 from M to N in FILE. With -p/--patch only diff hunks affecting
3301 from M to N in FILE. With -p/--patch only diff hunks affecting
3302 specified line range will be shown. This option requires --follow;
3302 specified line range will be shown. This option requires --follow;
3303 it can be specified multiple times. Currently, this option is not
3303 it can be specified multiple times. Currently, this option is not
3304 compatible with --graph. This option is experimental.
3304 compatible with --graph. This option is experimental.
3305
3305
3306 .. note::
3306 .. note::
3307
3307
3308 :hg:`log --patch` may generate unexpected diff output for merge
3308 :hg:`log --patch` may generate unexpected diff output for merge
3309 changesets, as it will only compare the merge changeset against
3309 changesets, as it will only compare the merge changeset against
3310 its first parent. Also, only files different from BOTH parents
3310 its first parent. Also, only files different from BOTH parents
3311 will appear in files:.
3311 will appear in files:.
3312
3312
3313 .. note::
3313 .. note::
3314
3314
3315 For performance reasons, :hg:`log FILE` may omit duplicate changes
3315 For performance reasons, :hg:`log FILE` may omit duplicate changes
3316 made on branches and will not show removals or mode changes. To
3316 made on branches and will not show removals or mode changes. To
3317 see all such changes, use the --removed switch.
3317 see all such changes, use the --removed switch.
3318
3318
3319 .. container:: verbose
3319 .. container:: verbose
3320
3320
3321 .. note::
3321 .. note::
3322
3322
3323 The history resulting from -L/--line-range options depends on diff
3323 The history resulting from -L/--line-range options depends on diff
3324 options; for instance if white-spaces are ignored, respective changes
3324 options; for instance if white-spaces are ignored, respective changes
3325 with only white-spaces in specified line range will not be listed.
3325 with only white-spaces in specified line range will not be listed.
3326
3326
3327 .. container:: verbose
3327 .. container:: verbose
3328
3328
3329 Some examples:
3329 Some examples:
3330
3330
3331 - changesets with full descriptions and file lists::
3331 - changesets with full descriptions and file lists::
3332
3332
3333 hg log -v
3333 hg log -v
3334
3334
3335 - changesets ancestral to the working directory::
3335 - changesets ancestral to the working directory::
3336
3336
3337 hg log -f
3337 hg log -f
3338
3338
3339 - last 10 commits on the current branch::
3339 - last 10 commits on the current branch::
3340
3340
3341 hg log -l 10 -b .
3341 hg log -l 10 -b .
3342
3342
3343 - changesets showing all modifications of a file, including removals::
3343 - changesets showing all modifications of a file, including removals::
3344
3344
3345 hg log --removed file.c
3345 hg log --removed file.c
3346
3346
3347 - all changesets that touch a directory, with diffs, excluding merges::
3347 - all changesets that touch a directory, with diffs, excluding merges::
3348
3348
3349 hg log -Mp lib/
3349 hg log -Mp lib/
3350
3350
3351 - all revision numbers that match a keyword::
3351 - all revision numbers that match a keyword::
3352
3352
3353 hg log -k bug --template "{rev}\\n"
3353 hg log -k bug --template "{rev}\\n"
3354
3354
3355 - the full hash identifier of the working directory parent::
3355 - the full hash identifier of the working directory parent::
3356
3356
3357 hg log -r . --template "{node}\\n"
3357 hg log -r . --template "{node}\\n"
3358
3358
3359 - list available log templates::
3359 - list available log templates::
3360
3360
3361 hg log -T list
3361 hg log -T list
3362
3362
3363 - check if a given changeset is included in a tagged release::
3363 - check if a given changeset is included in a tagged release::
3364
3364
3365 hg log -r "a21ccf and ancestor(1.9)"
3365 hg log -r "a21ccf and ancestor(1.9)"
3366
3366
3367 - find all changesets by some user in a date range::
3367 - find all changesets by some user in a date range::
3368
3368
3369 hg log -k alice -d "may 2008 to jul 2008"
3369 hg log -k alice -d "may 2008 to jul 2008"
3370
3370
3371 - summary of all changesets after the last tag::
3371 - summary of all changesets after the last tag::
3372
3372
3373 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3373 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3374
3374
3375 - changesets touching lines 13 to 23 for file.c::
3375 - changesets touching lines 13 to 23 for file.c::
3376
3376
3377 hg log -L file.c,13:23
3377 hg log -L file.c,13:23
3378
3378
3379 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3379 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3380 main.c with patch::
3380 main.c with patch::
3381
3381
3382 hg log -L file.c,13:23 -L main.c,2:6 -p
3382 hg log -L file.c,13:23 -L main.c,2:6 -p
3383
3383
3384 See :hg:`help dates` for a list of formats valid for -d/--date.
3384 See :hg:`help dates` for a list of formats valid for -d/--date.
3385
3385
3386 See :hg:`help revisions` for more about specifying and ordering
3386 See :hg:`help revisions` for more about specifying and ordering
3387 revisions.
3387 revisions.
3388
3388
3389 See :hg:`help templates` for more about pre-packaged styles and
3389 See :hg:`help templates` for more about pre-packaged styles and
3390 specifying custom templates. The default template used by the log
3390 specifying custom templates. The default template used by the log
3391 command can be customized via the ``ui.logtemplate`` configuration
3391 command can be customized via the ``ui.logtemplate`` configuration
3392 setting.
3392 setting.
3393
3393
3394 Returns 0 on success.
3394 Returns 0 on success.
3395
3395
3396 """
3396 """
3397 opts = pycompat.byteskwargs(opts)
3397 opts = pycompat.byteskwargs(opts)
3398 linerange = opts.get('line_range')
3398 linerange = opts.get('line_range')
3399
3399
3400 if linerange and not opts.get('follow'):
3400 if linerange and not opts.get('follow'):
3401 raise error.Abort(_('--line-range requires --follow'))
3401 raise error.Abort(_('--line-range requires --follow'))
3402
3402
3403 if linerange and pats:
3403 if linerange and pats:
3404 raise error.Abort(
3404 raise error.Abort(
3405 _('FILE arguments are not compatible with --line-range option')
3405 _('FILE arguments are not compatible with --line-range option')
3406 )
3406 )
3407
3407
3408 if opts.get('follow') and opts.get('rev'):
3408 if opts.get('follow') and opts.get('rev'):
3409 opts['rev'] = [revsetlang.formatspec('reverse(::%lr)', opts.get('rev'))]
3409 opts['rev'] = [revsetlang.formatspec('reverse(::%lr)', opts.get('rev'))]
3410 del opts['follow']
3410 del opts['follow']
3411
3411
3412 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3412 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3413 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
3413 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
3414 hunksfilter = None
3414 hunksfilter = None
3415
3415
3416 if opts.get('graph'):
3416 if opts.get('graph'):
3417 if linerange:
3417 if linerange:
3418 raise error.Abort(_('graph not supported with line range patterns'))
3418 raise error.Abort(_('graph not supported with line range patterns'))
3419 return cmdutil.graphlog(ui, repo, revs, filematcher, opts)
3419 return cmdutil.graphlog(ui, repo, revs, filematcher, opts)
3420
3420
3421 if linerange:
3421 if linerange:
3422 revs, lrfilematcher, hunksfilter = cmdutil.getloglinerangerevs(
3422 revs, lrfilematcher, hunksfilter = cmdutil.getloglinerangerevs(
3423 repo, revs, opts)
3423 repo, revs, opts)
3424
3424
3425 if filematcher is not None and lrfilematcher is not None:
3425 if filematcher is not None and lrfilematcher is not None:
3426 basefilematcher = filematcher
3426 basefilematcher = filematcher
3427
3427
3428 def filematcher(rev):
3428 def filematcher(rev):
3429 files = (basefilematcher(rev).files()
3429 files = (basefilematcher(rev).files()
3430 + lrfilematcher(rev).files())
3430 + lrfilematcher(rev).files())
3431 return scmutil.matchfiles(repo, files)
3431 return scmutil.matchfiles(repo, files)
3432
3432
3433 elif filematcher is None:
3433 elif filematcher is None:
3434 filematcher = lrfilematcher
3434 filematcher = lrfilematcher
3435
3435
3436 limit = cmdutil.loglimit(opts)
3437 count = 0
3438
3439 getrenamed = None
3436 getrenamed = None
3440 if opts.get('copies'):
3437 if opts.get('copies'):
3441 endrev = None
3438 endrev = None
3442 if opts.get('rev'):
3439 if opts.get('rev'):
3443 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
3440 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
3444 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3441 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3445
3442
3446 ui.pager('log')
3443 ui.pager('log')
3447 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3444 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3448 for rev in revs:
3445 for rev in revs:
3449 if count == limit:
3450 break
3451 ctx = repo[rev]
3446 ctx = repo[rev]
3452 copies = None
3447 copies = None
3453 if getrenamed is not None and rev:
3448 if getrenamed is not None and rev:
3454 copies = []
3449 copies = []
3455 for fn in ctx.files():
3450 for fn in ctx.files():
3456 rename = getrenamed(fn, rev)
3451 rename = getrenamed(fn, rev)
3457 if rename:
3452 if rename:
3458 copies.append((fn, rename[0]))
3453 copies.append((fn, rename[0]))
3459 if filematcher:
3454 if filematcher:
3460 revmatchfn = filematcher(ctx.rev())
3455 revmatchfn = filematcher(ctx.rev())
3461 else:
3456 else:
3462 revmatchfn = None
3457 revmatchfn = None
3463 if hunksfilter:
3458 if hunksfilter:
3464 revhunksfilter = hunksfilter(rev)
3459 revhunksfilter = hunksfilter(rev)
3465 else:
3460 else:
3466 revhunksfilter = None
3461 revhunksfilter = None
3467 displayer.show(ctx, copies=copies, matchfn=revmatchfn,
3462 displayer.show(ctx, copies=copies, matchfn=revmatchfn,
3468 hunksfilterfn=revhunksfilter)
3463 hunksfilterfn=revhunksfilter)
3469 if displayer.flush(ctx):
3464 displayer.flush(ctx)
3470 count += 1
3471
3465
3472 displayer.close()
3466 displayer.close()
3473
3467
3474 @command('manifest',
3468 @command('manifest',
3475 [('r', 'rev', '', _('revision to display'), _('REV')),
3469 [('r', 'rev', '', _('revision to display'), _('REV')),
3476 ('', 'all', False, _("list files from all revisions"))]
3470 ('', 'all', False, _("list files from all revisions"))]
3477 + formatteropts,
3471 + formatteropts,
3478 _('[-r REV]'), cmdtype=readonly)
3472 _('[-r REV]'), cmdtype=readonly)
3479 def manifest(ui, repo, node=None, rev=None, **opts):
3473 def manifest(ui, repo, node=None, rev=None, **opts):
3480 """output the current or given revision of the project manifest
3474 """output the current or given revision of the project manifest
3481
3475
3482 Print a list of version controlled files for the given revision.
3476 Print a list of version controlled files for the given revision.
3483 If no revision is given, the first parent of the working directory
3477 If no revision is given, the first parent of the working directory
3484 is used, or the null revision if no revision is checked out.
3478 is used, or the null revision if no revision is checked out.
3485
3479
3486 With -v, print file permissions, symlink and executable bits.
3480 With -v, print file permissions, symlink and executable bits.
3487 With --debug, print file revision hashes.
3481 With --debug, print file revision hashes.
3488
3482
3489 If option --all is specified, the list of all files from all revisions
3483 If option --all is specified, the list of all files from all revisions
3490 is printed. This includes deleted and renamed files.
3484 is printed. This includes deleted and renamed files.
3491
3485
3492 Returns 0 on success.
3486 Returns 0 on success.
3493 """
3487 """
3494 opts = pycompat.byteskwargs(opts)
3488 opts = pycompat.byteskwargs(opts)
3495 fm = ui.formatter('manifest', opts)
3489 fm = ui.formatter('manifest', opts)
3496
3490
3497 if opts.get('all'):
3491 if opts.get('all'):
3498 if rev or node:
3492 if rev or node:
3499 raise error.Abort(_("can't specify a revision with --all"))
3493 raise error.Abort(_("can't specify a revision with --all"))
3500
3494
3501 res = []
3495 res = []
3502 prefix = "data/"
3496 prefix = "data/"
3503 suffix = ".i"
3497 suffix = ".i"
3504 plen = len(prefix)
3498 plen = len(prefix)
3505 slen = len(suffix)
3499 slen = len(suffix)
3506 with repo.lock():
3500 with repo.lock():
3507 for fn, b, size in repo.store.datafiles():
3501 for fn, b, size in repo.store.datafiles():
3508 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3502 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3509 res.append(fn[plen:-slen])
3503 res.append(fn[plen:-slen])
3510 ui.pager('manifest')
3504 ui.pager('manifest')
3511 for f in res:
3505 for f in res:
3512 fm.startitem()
3506 fm.startitem()
3513 fm.write("path", '%s\n', f)
3507 fm.write("path", '%s\n', f)
3514 fm.end()
3508 fm.end()
3515 return
3509 return
3516
3510
3517 if rev and node:
3511 if rev and node:
3518 raise error.Abort(_("please specify just one revision"))
3512 raise error.Abort(_("please specify just one revision"))
3519
3513
3520 if not node:
3514 if not node:
3521 node = rev
3515 node = rev
3522
3516
3523 char = {'l': '@', 'x': '*', '': ''}
3517 char = {'l': '@', 'x': '*', '': ''}
3524 mode = {'l': '644', 'x': '755', '': '644'}
3518 mode = {'l': '644', 'x': '755', '': '644'}
3525 if node:
3519 if node:
3526 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3520 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3527 ctx = scmutil.revsingle(repo, node)
3521 ctx = scmutil.revsingle(repo, node)
3528 mf = ctx.manifest()
3522 mf = ctx.manifest()
3529 ui.pager('manifest')
3523 ui.pager('manifest')
3530 for f in ctx:
3524 for f in ctx:
3531 fm.startitem()
3525 fm.startitem()
3532 fl = ctx[f].flags()
3526 fl = ctx[f].flags()
3533 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3527 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3534 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3528 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3535 fm.write('path', '%s\n', f)
3529 fm.write('path', '%s\n', f)
3536 fm.end()
3530 fm.end()
3537
3531
3538 @command('^merge',
3532 @command('^merge',
3539 [('f', 'force', None,
3533 [('f', 'force', None,
3540 _('force a merge including outstanding changes (DEPRECATED)')),
3534 _('force a merge including outstanding changes (DEPRECATED)')),
3541 ('r', 'rev', '', _('revision to merge'), _('REV')),
3535 ('r', 'rev', '', _('revision to merge'), _('REV')),
3542 ('P', 'preview', None,
3536 ('P', 'preview', None,
3543 _('review revisions to merge (no merge is performed)'))
3537 _('review revisions to merge (no merge is performed)'))
3544 ] + mergetoolopts,
3538 ] + mergetoolopts,
3545 _('[-P] [[-r] REV]'))
3539 _('[-P] [[-r] REV]'))
3546 def merge(ui, repo, node=None, **opts):
3540 def merge(ui, repo, node=None, **opts):
3547 """merge another revision into working directory
3541 """merge another revision into working directory
3548
3542
3549 The current working directory is updated with all changes made in
3543 The current working directory is updated with all changes made in
3550 the requested revision since the last common predecessor revision.
3544 the requested revision since the last common predecessor revision.
3551
3545
3552 Files that changed between either parent are marked as changed for
3546 Files that changed between either parent are marked as changed for
3553 the next commit and a commit must be performed before any further
3547 the next commit and a commit must be performed before any further
3554 updates to the repository are allowed. The next commit will have
3548 updates to the repository are allowed. The next commit will have
3555 two parents.
3549 two parents.
3556
3550
3557 ``--tool`` can be used to specify the merge tool used for file
3551 ``--tool`` can be used to specify the merge tool used for file
3558 merges. It overrides the HGMERGE environment variable and your
3552 merges. It overrides the HGMERGE environment variable and your
3559 configuration files. See :hg:`help merge-tools` for options.
3553 configuration files. See :hg:`help merge-tools` for options.
3560
3554
3561 If no revision is specified, the working directory's parent is a
3555 If no revision is specified, the working directory's parent is a
3562 head revision, and the current branch contains exactly one other
3556 head revision, and the current branch contains exactly one other
3563 head, the other head is merged with by default. Otherwise, an
3557 head, the other head is merged with by default. Otherwise, an
3564 explicit revision with which to merge with must be provided.
3558 explicit revision with which to merge with must be provided.
3565
3559
3566 See :hg:`help resolve` for information on handling file conflicts.
3560 See :hg:`help resolve` for information on handling file conflicts.
3567
3561
3568 To undo an uncommitted merge, use :hg:`update --clean .` which
3562 To undo an uncommitted merge, use :hg:`update --clean .` which
3569 will check out a clean copy of the original merge parent, losing
3563 will check out a clean copy of the original merge parent, losing
3570 all changes.
3564 all changes.
3571
3565
3572 Returns 0 on success, 1 if there are unresolved files.
3566 Returns 0 on success, 1 if there are unresolved files.
3573 """
3567 """
3574
3568
3575 opts = pycompat.byteskwargs(opts)
3569 opts = pycompat.byteskwargs(opts)
3576 if opts.get('rev') and node:
3570 if opts.get('rev') and node:
3577 raise error.Abort(_("please specify just one revision"))
3571 raise error.Abort(_("please specify just one revision"))
3578 if not node:
3572 if not node:
3579 node = opts.get('rev')
3573 node = opts.get('rev')
3580
3574
3581 if node:
3575 if node:
3582 node = scmutil.revsingle(repo, node).node()
3576 node = scmutil.revsingle(repo, node).node()
3583
3577
3584 if not node:
3578 if not node:
3585 node = repo[destutil.destmerge(repo)].node()
3579 node = repo[destutil.destmerge(repo)].node()
3586
3580
3587 if opts.get('preview'):
3581 if opts.get('preview'):
3588 # find nodes that are ancestors of p2 but not of p1
3582 # find nodes that are ancestors of p2 but not of p1
3589 p1 = repo.lookup('.')
3583 p1 = repo.lookup('.')
3590 p2 = repo.lookup(node)
3584 p2 = repo.lookup(node)
3591 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3585 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3592
3586
3593 displayer = cmdutil.show_changeset(ui, repo, opts)
3587 displayer = cmdutil.show_changeset(ui, repo, opts)
3594 for node in nodes:
3588 for node in nodes:
3595 displayer.show(repo[node])
3589 displayer.show(repo[node])
3596 displayer.close()
3590 displayer.close()
3597 return 0
3591 return 0
3598
3592
3599 try:
3593 try:
3600 # ui.forcemerge is an internal variable, do not document
3594 # ui.forcemerge is an internal variable, do not document
3601 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
3595 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
3602 force = opts.get('force')
3596 force = opts.get('force')
3603 labels = ['working copy', 'merge rev']
3597 labels = ['working copy', 'merge rev']
3604 return hg.merge(repo, node, force=force, mergeforce=force,
3598 return hg.merge(repo, node, force=force, mergeforce=force,
3605 labels=labels)
3599 labels=labels)
3606 finally:
3600 finally:
3607 ui.setconfig('ui', 'forcemerge', '', 'merge')
3601 ui.setconfig('ui', 'forcemerge', '', 'merge')
3608
3602
3609 @command('outgoing|out',
3603 @command('outgoing|out',
3610 [('f', 'force', None, _('run even when the destination is unrelated')),
3604 [('f', 'force', None, _('run even when the destination is unrelated')),
3611 ('r', 'rev', [],
3605 ('r', 'rev', [],
3612 _('a changeset intended to be included in the destination'), _('REV')),
3606 _('a changeset intended to be included in the destination'), _('REV')),
3613 ('n', 'newest-first', None, _('show newest record first')),
3607 ('n', 'newest-first', None, _('show newest record first')),
3614 ('B', 'bookmarks', False, _('compare bookmarks')),
3608 ('B', 'bookmarks', False, _('compare bookmarks')),
3615 ('b', 'branch', [], _('a specific branch you would like to push'),
3609 ('b', 'branch', [], _('a specific branch you would like to push'),
3616 _('BRANCH')),
3610 _('BRANCH')),
3617 ] + logopts + remoteopts + subrepoopts,
3611 ] + logopts + remoteopts + subrepoopts,
3618 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3612 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3619 def outgoing(ui, repo, dest=None, **opts):
3613 def outgoing(ui, repo, dest=None, **opts):
3620 """show changesets not found in the destination
3614 """show changesets not found in the destination
3621
3615
3622 Show changesets not found in the specified destination repository
3616 Show changesets not found in the specified destination repository
3623 or the default push location. These are the changesets that would
3617 or the default push location. These are the changesets that would
3624 be pushed if a push was requested.
3618 be pushed if a push was requested.
3625
3619
3626 See pull for details of valid destination formats.
3620 See pull for details of valid destination formats.
3627
3621
3628 .. container:: verbose
3622 .. container:: verbose
3629
3623
3630 With -B/--bookmarks, the result of bookmark comparison between
3624 With -B/--bookmarks, the result of bookmark comparison between
3631 local and remote repositories is displayed. With -v/--verbose,
3625 local and remote repositories is displayed. With -v/--verbose,
3632 status is also displayed for each bookmark like below::
3626 status is also displayed for each bookmark like below::
3633
3627
3634 BM1 01234567890a added
3628 BM1 01234567890a added
3635 BM2 deleted
3629 BM2 deleted
3636 BM3 234567890abc advanced
3630 BM3 234567890abc advanced
3637 BM4 34567890abcd diverged
3631 BM4 34567890abcd diverged
3638 BM5 4567890abcde changed
3632 BM5 4567890abcde changed
3639
3633
3640 The action taken when pushing depends on the
3634 The action taken when pushing depends on the
3641 status of each bookmark:
3635 status of each bookmark:
3642
3636
3643 :``added``: push with ``-B`` will create it
3637 :``added``: push with ``-B`` will create it
3644 :``deleted``: push with ``-B`` will delete it
3638 :``deleted``: push with ``-B`` will delete it
3645 :``advanced``: push will update it
3639 :``advanced``: push will update it
3646 :``diverged``: push with ``-B`` will update it
3640 :``diverged``: push with ``-B`` will update it
3647 :``changed``: push with ``-B`` will update it
3641 :``changed``: push with ``-B`` will update it
3648
3642
3649 From the point of view of pushing behavior, bookmarks
3643 From the point of view of pushing behavior, bookmarks
3650 existing only in the remote repository are treated as
3644 existing only in the remote repository are treated as
3651 ``deleted``, even if it is in fact added remotely.
3645 ``deleted``, even if it is in fact added remotely.
3652
3646
3653 Returns 0 if there are outgoing changes, 1 otherwise.
3647 Returns 0 if there are outgoing changes, 1 otherwise.
3654 """
3648 """
3655 opts = pycompat.byteskwargs(opts)
3649 opts = pycompat.byteskwargs(opts)
3656 if opts.get('graph'):
3650 if opts.get('graph'):
3657 cmdutil.checkunsupportedgraphflags([], opts)
3651 cmdutil.checkunsupportedgraphflags([], opts)
3658 o, other = hg._outgoing(ui, repo, dest, opts)
3652 o, other = hg._outgoing(ui, repo, dest, opts)
3659 if not o:
3653 if not o:
3660 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3654 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3661 return
3655 return
3662
3656
3663 revdag = cmdutil.graphrevs(repo, o, opts)
3657 revdag = cmdutil.graphrevs(repo, o, opts)
3664 ui.pager('outgoing')
3658 ui.pager('outgoing')
3665 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3659 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3666 cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
3660 cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
3667 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3661 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3668 return 0
3662 return 0
3669
3663
3670 if opts.get('bookmarks'):
3664 if opts.get('bookmarks'):
3671 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3665 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3672 dest, branches = hg.parseurl(dest, opts.get('branch'))
3666 dest, branches = hg.parseurl(dest, opts.get('branch'))
3673 other = hg.peer(repo, opts, dest)
3667 other = hg.peer(repo, opts, dest)
3674 if 'bookmarks' not in other.listkeys('namespaces'):
3668 if 'bookmarks' not in other.listkeys('namespaces'):
3675 ui.warn(_("remote doesn't support bookmarks\n"))
3669 ui.warn(_("remote doesn't support bookmarks\n"))
3676 return 0
3670 return 0
3677 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3671 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3678 ui.pager('outgoing')
3672 ui.pager('outgoing')
3679 return bookmarks.outgoing(ui, repo, other)
3673 return bookmarks.outgoing(ui, repo, other)
3680
3674
3681 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3675 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3682 try:
3676 try:
3683 return hg.outgoing(ui, repo, dest, opts)
3677 return hg.outgoing(ui, repo, dest, opts)
3684 finally:
3678 finally:
3685 del repo._subtoppath
3679 del repo._subtoppath
3686
3680
3687 @command('parents',
3681 @command('parents',
3688 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3682 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3689 ] + templateopts,
3683 ] + templateopts,
3690 _('[-r REV] [FILE]'),
3684 _('[-r REV] [FILE]'),
3691 inferrepo=True)
3685 inferrepo=True)
3692 def parents(ui, repo, file_=None, **opts):
3686 def parents(ui, repo, file_=None, **opts):
3693 """show the parents of the working directory or revision (DEPRECATED)
3687 """show the parents of the working directory or revision (DEPRECATED)
3694
3688
3695 Print the working directory's parent revisions. If a revision is
3689 Print the working directory's parent revisions. If a revision is
3696 given via -r/--rev, the parent of that revision will be printed.
3690 given via -r/--rev, the parent of that revision will be printed.
3697 If a file argument is given, the revision in which the file was
3691 If a file argument is given, the revision in which the file was
3698 last changed (before the working directory revision or the
3692 last changed (before the working directory revision or the
3699 argument to --rev if given) is printed.
3693 argument to --rev if given) is printed.
3700
3694
3701 This command is equivalent to::
3695 This command is equivalent to::
3702
3696
3703 hg log -r "p1()+p2()" or
3697 hg log -r "p1()+p2()" or
3704 hg log -r "p1(REV)+p2(REV)" or
3698 hg log -r "p1(REV)+p2(REV)" or
3705 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3699 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3706 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3700 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3707
3701
3708 See :hg:`summary` and :hg:`help revsets` for related information.
3702 See :hg:`summary` and :hg:`help revsets` for related information.
3709
3703
3710 Returns 0 on success.
3704 Returns 0 on success.
3711 """
3705 """
3712
3706
3713 opts = pycompat.byteskwargs(opts)
3707 opts = pycompat.byteskwargs(opts)
3714 rev = opts.get('rev')
3708 rev = opts.get('rev')
3715 if rev:
3709 if rev:
3716 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3710 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3717 ctx = scmutil.revsingle(repo, rev, None)
3711 ctx = scmutil.revsingle(repo, rev, None)
3718
3712
3719 if file_:
3713 if file_:
3720 m = scmutil.match(ctx, (file_,), opts)
3714 m = scmutil.match(ctx, (file_,), opts)
3721 if m.anypats() or len(m.files()) != 1:
3715 if m.anypats() or len(m.files()) != 1:
3722 raise error.Abort(_('can only specify an explicit filename'))
3716 raise error.Abort(_('can only specify an explicit filename'))
3723 file_ = m.files()[0]
3717 file_ = m.files()[0]
3724 filenodes = []
3718 filenodes = []
3725 for cp in ctx.parents():
3719 for cp in ctx.parents():
3726 if not cp:
3720 if not cp:
3727 continue
3721 continue
3728 try:
3722 try:
3729 filenodes.append(cp.filenode(file_))
3723 filenodes.append(cp.filenode(file_))
3730 except error.LookupError:
3724 except error.LookupError:
3731 pass
3725 pass
3732 if not filenodes:
3726 if not filenodes:
3733 raise error.Abort(_("'%s' not found in manifest!") % file_)
3727 raise error.Abort(_("'%s' not found in manifest!") % file_)
3734 p = []
3728 p = []
3735 for fn in filenodes:
3729 for fn in filenodes:
3736 fctx = repo.filectx(file_, fileid=fn)
3730 fctx = repo.filectx(file_, fileid=fn)
3737 p.append(fctx.node())
3731 p.append(fctx.node())
3738 else:
3732 else:
3739 p = [cp.node() for cp in ctx.parents()]
3733 p = [cp.node() for cp in ctx.parents()]
3740
3734
3741 displayer = cmdutil.show_changeset(ui, repo, opts)
3735 displayer = cmdutil.show_changeset(ui, repo, opts)
3742 for n in p:
3736 for n in p:
3743 if n != nullid:
3737 if n != nullid:
3744 displayer.show(repo[n])
3738 displayer.show(repo[n])
3745 displayer.close()
3739 displayer.close()
3746
3740
3747 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True,
3741 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True,
3748 cmdtype=readonly)
3742 cmdtype=readonly)
3749 def paths(ui, repo, search=None, **opts):
3743 def paths(ui, repo, search=None, **opts):
3750 """show aliases for remote repositories
3744 """show aliases for remote repositories
3751
3745
3752 Show definition of symbolic path name NAME. If no name is given,
3746 Show definition of symbolic path name NAME. If no name is given,
3753 show definition of all available names.
3747 show definition of all available names.
3754
3748
3755 Option -q/--quiet suppresses all output when searching for NAME
3749 Option -q/--quiet suppresses all output when searching for NAME
3756 and shows only the path names when listing all definitions.
3750 and shows only the path names when listing all definitions.
3757
3751
3758 Path names are defined in the [paths] section of your
3752 Path names are defined in the [paths] section of your
3759 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3753 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3760 repository, ``.hg/hgrc`` is used, too.
3754 repository, ``.hg/hgrc`` is used, too.
3761
3755
3762 The path names ``default`` and ``default-push`` have a special
3756 The path names ``default`` and ``default-push`` have a special
3763 meaning. When performing a push or pull operation, they are used
3757 meaning. When performing a push or pull operation, they are used
3764 as fallbacks if no location is specified on the command-line.
3758 as fallbacks if no location is specified on the command-line.
3765 When ``default-push`` is set, it will be used for push and
3759 When ``default-push`` is set, it will be used for push and
3766 ``default`` will be used for pull; otherwise ``default`` is used
3760 ``default`` will be used for pull; otherwise ``default`` is used
3767 as the fallback for both. When cloning a repository, the clone
3761 as the fallback for both. When cloning a repository, the clone
3768 source is written as ``default`` in ``.hg/hgrc``.
3762 source is written as ``default`` in ``.hg/hgrc``.
3769
3763
3770 .. note::
3764 .. note::
3771
3765
3772 ``default`` and ``default-push`` apply to all inbound (e.g.
3766 ``default`` and ``default-push`` apply to all inbound (e.g.
3773 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3767 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3774 and :hg:`bundle`) operations.
3768 and :hg:`bundle`) operations.
3775
3769
3776 See :hg:`help urls` for more information.
3770 See :hg:`help urls` for more information.
3777
3771
3778 Returns 0 on success.
3772 Returns 0 on success.
3779 """
3773 """
3780
3774
3781 opts = pycompat.byteskwargs(opts)
3775 opts = pycompat.byteskwargs(opts)
3782 ui.pager('paths')
3776 ui.pager('paths')
3783 if search:
3777 if search:
3784 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3778 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3785 if name == search]
3779 if name == search]
3786 else:
3780 else:
3787 pathitems = sorted(ui.paths.iteritems())
3781 pathitems = sorted(ui.paths.iteritems())
3788
3782
3789 fm = ui.formatter('paths', opts)
3783 fm = ui.formatter('paths', opts)
3790 if fm.isplain():
3784 if fm.isplain():
3791 hidepassword = util.hidepassword
3785 hidepassword = util.hidepassword
3792 else:
3786 else:
3793 hidepassword = str
3787 hidepassword = str
3794 if ui.quiet:
3788 if ui.quiet:
3795 namefmt = '%s\n'
3789 namefmt = '%s\n'
3796 else:
3790 else:
3797 namefmt = '%s = '
3791 namefmt = '%s = '
3798 showsubopts = not search and not ui.quiet
3792 showsubopts = not search and not ui.quiet
3799
3793
3800 for name, path in pathitems:
3794 for name, path in pathitems:
3801 fm.startitem()
3795 fm.startitem()
3802 fm.condwrite(not search, 'name', namefmt, name)
3796 fm.condwrite(not search, 'name', namefmt, name)
3803 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3797 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3804 for subopt, value in sorted(path.suboptions.items()):
3798 for subopt, value in sorted(path.suboptions.items()):
3805 assert subopt not in ('name', 'url')
3799 assert subopt not in ('name', 'url')
3806 if showsubopts:
3800 if showsubopts:
3807 fm.plain('%s:%s = ' % (name, subopt))
3801 fm.plain('%s:%s = ' % (name, subopt))
3808 fm.condwrite(showsubopts, subopt, '%s\n', value)
3802 fm.condwrite(showsubopts, subopt, '%s\n', value)
3809
3803
3810 fm.end()
3804 fm.end()
3811
3805
3812 if search and not pathitems:
3806 if search and not pathitems:
3813 if not ui.quiet:
3807 if not ui.quiet:
3814 ui.warn(_("not found!\n"))
3808 ui.warn(_("not found!\n"))
3815 return 1
3809 return 1
3816 else:
3810 else:
3817 return 0
3811 return 0
3818
3812
3819 @command('phase',
3813 @command('phase',
3820 [('p', 'public', False, _('set changeset phase to public')),
3814 [('p', 'public', False, _('set changeset phase to public')),
3821 ('d', 'draft', False, _('set changeset phase to draft')),
3815 ('d', 'draft', False, _('set changeset phase to draft')),
3822 ('s', 'secret', False, _('set changeset phase to secret')),
3816 ('s', 'secret', False, _('set changeset phase to secret')),
3823 ('f', 'force', False, _('allow to move boundary backward')),
3817 ('f', 'force', False, _('allow to move boundary backward')),
3824 ('r', 'rev', [], _('target revision'), _('REV')),
3818 ('r', 'rev', [], _('target revision'), _('REV')),
3825 ],
3819 ],
3826 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3820 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3827 def phase(ui, repo, *revs, **opts):
3821 def phase(ui, repo, *revs, **opts):
3828 """set or show the current phase name
3822 """set or show the current phase name
3829
3823
3830 With no argument, show the phase name of the current revision(s).
3824 With no argument, show the phase name of the current revision(s).
3831
3825
3832 With one of -p/--public, -d/--draft or -s/--secret, change the
3826 With one of -p/--public, -d/--draft or -s/--secret, change the
3833 phase value of the specified revisions.
3827 phase value of the specified revisions.
3834
3828
3835 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
3829 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
3836 lower phase to a higher phase. Phases are ordered as follows::
3830 lower phase to a higher phase. Phases are ordered as follows::
3837
3831
3838 public < draft < secret
3832 public < draft < secret
3839
3833
3840 Returns 0 on success, 1 if some phases could not be changed.
3834 Returns 0 on success, 1 if some phases could not be changed.
3841
3835
3842 (For more information about the phases concept, see :hg:`help phases`.)
3836 (For more information about the phases concept, see :hg:`help phases`.)
3843 """
3837 """
3844 opts = pycompat.byteskwargs(opts)
3838 opts = pycompat.byteskwargs(opts)
3845 # search for a unique phase argument
3839 # search for a unique phase argument
3846 targetphase = None
3840 targetphase = None
3847 for idx, name in enumerate(phases.phasenames):
3841 for idx, name in enumerate(phases.phasenames):
3848 if opts[name]:
3842 if opts[name]:
3849 if targetphase is not None:
3843 if targetphase is not None:
3850 raise error.Abort(_('only one phase can be specified'))
3844 raise error.Abort(_('only one phase can be specified'))
3851 targetphase = idx
3845 targetphase = idx
3852
3846
3853 # look for specified revision
3847 # look for specified revision
3854 revs = list(revs)
3848 revs = list(revs)
3855 revs.extend(opts['rev'])
3849 revs.extend(opts['rev'])
3856 if not revs:
3850 if not revs:
3857 # display both parents as the second parent phase can influence
3851 # display both parents as the second parent phase can influence
3858 # the phase of a merge commit
3852 # the phase of a merge commit
3859 revs = [c.rev() for c in repo[None].parents()]
3853 revs = [c.rev() for c in repo[None].parents()]
3860
3854
3861 revs = scmutil.revrange(repo, revs)
3855 revs = scmutil.revrange(repo, revs)
3862
3856
3863 lock = None
3857 lock = None
3864 ret = 0
3858 ret = 0
3865 if targetphase is None:
3859 if targetphase is None:
3866 # display
3860 # display
3867 for r in revs:
3861 for r in revs:
3868 ctx = repo[r]
3862 ctx = repo[r]
3869 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
3863 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
3870 else:
3864 else:
3871 tr = None
3865 tr = None
3872 lock = repo.lock()
3866 lock = repo.lock()
3873 try:
3867 try:
3874 tr = repo.transaction("phase")
3868 tr = repo.transaction("phase")
3875 # set phase
3869 # set phase
3876 if not revs:
3870 if not revs:
3877 raise error.Abort(_('empty revision set'))
3871 raise error.Abort(_('empty revision set'))
3878 nodes = [repo[r].node() for r in revs]
3872 nodes = [repo[r].node() for r in revs]
3879 # moving revision from public to draft may hide them
3873 # moving revision from public to draft may hide them
3880 # We have to check result on an unfiltered repository
3874 # We have to check result on an unfiltered repository
3881 unfi = repo.unfiltered()
3875 unfi = repo.unfiltered()
3882 getphase = unfi._phasecache.phase
3876 getphase = unfi._phasecache.phase
3883 olddata = [getphase(unfi, r) for r in unfi]
3877 olddata = [getphase(unfi, r) for r in unfi]
3884 phases.advanceboundary(repo, tr, targetphase, nodes)
3878 phases.advanceboundary(repo, tr, targetphase, nodes)
3885 if opts['force']:
3879 if opts['force']:
3886 phases.retractboundary(repo, tr, targetphase, nodes)
3880 phases.retractboundary(repo, tr, targetphase, nodes)
3887 tr.close()
3881 tr.close()
3888 finally:
3882 finally:
3889 if tr is not None:
3883 if tr is not None:
3890 tr.release()
3884 tr.release()
3891 lock.release()
3885 lock.release()
3892 getphase = unfi._phasecache.phase
3886 getphase = unfi._phasecache.phase
3893 newdata = [getphase(unfi, r) for r in unfi]
3887 newdata = [getphase(unfi, r) for r in unfi]
3894 changes = sum(newdata[r] != olddata[r] for r in unfi)
3888 changes = sum(newdata[r] != olddata[r] for r in unfi)
3895 cl = unfi.changelog
3889 cl = unfi.changelog
3896 rejected = [n for n in nodes
3890 rejected = [n for n in nodes
3897 if newdata[cl.rev(n)] < targetphase]
3891 if newdata[cl.rev(n)] < targetphase]
3898 if rejected:
3892 if rejected:
3899 ui.warn(_('cannot move %i changesets to a higher '
3893 ui.warn(_('cannot move %i changesets to a higher '
3900 'phase, use --force\n') % len(rejected))
3894 'phase, use --force\n') % len(rejected))
3901 ret = 1
3895 ret = 1
3902 if changes:
3896 if changes:
3903 msg = _('phase changed for %i changesets\n') % changes
3897 msg = _('phase changed for %i changesets\n') % changes
3904 if ret:
3898 if ret:
3905 ui.status(msg)
3899 ui.status(msg)
3906 else:
3900 else:
3907 ui.note(msg)
3901 ui.note(msg)
3908 else:
3902 else:
3909 ui.warn(_('no phases changed\n'))
3903 ui.warn(_('no phases changed\n'))
3910 return ret
3904 return ret
3911
3905
3912 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
3906 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
3913 """Run after a changegroup has been added via pull/unbundle
3907 """Run after a changegroup has been added via pull/unbundle
3914
3908
3915 This takes arguments below:
3909 This takes arguments below:
3916
3910
3917 :modheads: change of heads by pull/unbundle
3911 :modheads: change of heads by pull/unbundle
3918 :optupdate: updating working directory is needed or not
3912 :optupdate: updating working directory is needed or not
3919 :checkout: update destination revision (or None to default destination)
3913 :checkout: update destination revision (or None to default destination)
3920 :brev: a name, which might be a bookmark to be activated after updating
3914 :brev: a name, which might be a bookmark to be activated after updating
3921 """
3915 """
3922 if modheads == 0:
3916 if modheads == 0:
3923 return
3917 return
3924 if optupdate:
3918 if optupdate:
3925 try:
3919 try:
3926 return hg.updatetotally(ui, repo, checkout, brev)
3920 return hg.updatetotally(ui, repo, checkout, brev)
3927 except error.UpdateAbort as inst:
3921 except error.UpdateAbort as inst:
3928 msg = _("not updating: %s") % str(inst)
3922 msg = _("not updating: %s") % str(inst)
3929 hint = inst.hint
3923 hint = inst.hint
3930 raise error.UpdateAbort(msg, hint=hint)
3924 raise error.UpdateAbort(msg, hint=hint)
3931 if modheads > 1:
3925 if modheads > 1:
3932 currentbranchheads = len(repo.branchheads())
3926 currentbranchheads = len(repo.branchheads())
3933 if currentbranchheads == modheads:
3927 if currentbranchheads == modheads:
3934 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3928 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3935 elif currentbranchheads > 1:
3929 elif currentbranchheads > 1:
3936 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
3930 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
3937 "merge)\n"))
3931 "merge)\n"))
3938 else:
3932 else:
3939 ui.status(_("(run 'hg heads' to see heads)\n"))
3933 ui.status(_("(run 'hg heads' to see heads)\n"))
3940 elif not ui.configbool('commands', 'update.requiredest'):
3934 elif not ui.configbool('commands', 'update.requiredest'):
3941 ui.status(_("(run 'hg update' to get a working copy)\n"))
3935 ui.status(_("(run 'hg update' to get a working copy)\n"))
3942
3936
3943 @command('^pull',
3937 @command('^pull',
3944 [('u', 'update', None,
3938 [('u', 'update', None,
3945 _('update to new branch head if new descendants were pulled')),
3939 _('update to new branch head if new descendants were pulled')),
3946 ('f', 'force', None, _('run even when remote repository is unrelated')),
3940 ('f', 'force', None, _('run even when remote repository is unrelated')),
3947 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3941 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3948 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3942 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3949 ('b', 'branch', [], _('a specific branch you would like to pull'),
3943 ('b', 'branch', [], _('a specific branch you would like to pull'),
3950 _('BRANCH')),
3944 _('BRANCH')),
3951 ] + remoteopts,
3945 ] + remoteopts,
3952 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3946 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3953 def pull(ui, repo, source="default", **opts):
3947 def pull(ui, repo, source="default", **opts):
3954 """pull changes from the specified source
3948 """pull changes from the specified source
3955
3949
3956 Pull changes from a remote repository to a local one.
3950 Pull changes from a remote repository to a local one.
3957
3951
3958 This finds all changes from the repository at the specified path
3952 This finds all changes from the repository at the specified path
3959 or URL and adds them to a local repository (the current one unless
3953 or URL and adds them to a local repository (the current one unless
3960 -R is specified). By default, this does not update the copy of the
3954 -R is specified). By default, this does not update the copy of the
3961 project in the working directory.
3955 project in the working directory.
3962
3956
3963 Use :hg:`incoming` if you want to see what would have been added
3957 Use :hg:`incoming` if you want to see what would have been added
3964 by a pull at the time you issued this command. If you then decide
3958 by a pull at the time you issued this command. If you then decide
3965 to add those changes to the repository, you should use :hg:`pull
3959 to add those changes to the repository, you should use :hg:`pull
3966 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3960 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3967
3961
3968 If SOURCE is omitted, the 'default' path will be used.
3962 If SOURCE is omitted, the 'default' path will be used.
3969 See :hg:`help urls` for more information.
3963 See :hg:`help urls` for more information.
3970
3964
3971 Specifying bookmark as ``.`` is equivalent to specifying the active
3965 Specifying bookmark as ``.`` is equivalent to specifying the active
3972 bookmark's name.
3966 bookmark's name.
3973
3967
3974 Returns 0 on success, 1 if an update had unresolved files.
3968 Returns 0 on success, 1 if an update had unresolved files.
3975 """
3969 """
3976
3970
3977 opts = pycompat.byteskwargs(opts)
3971 opts = pycompat.byteskwargs(opts)
3978 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
3972 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
3979 msg = _('update destination required by configuration')
3973 msg = _('update destination required by configuration')
3980 hint = _('use hg pull followed by hg update DEST')
3974 hint = _('use hg pull followed by hg update DEST')
3981 raise error.Abort(msg, hint=hint)
3975 raise error.Abort(msg, hint=hint)
3982
3976
3983 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3977 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3984 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3978 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3985 other = hg.peer(repo, opts, source)
3979 other = hg.peer(repo, opts, source)
3986 try:
3980 try:
3987 revs, checkout = hg.addbranchrevs(repo, other, branches,
3981 revs, checkout = hg.addbranchrevs(repo, other, branches,
3988 opts.get('rev'))
3982 opts.get('rev'))
3989
3983
3990
3984
3991 pullopargs = {}
3985 pullopargs = {}
3992 if opts.get('bookmark'):
3986 if opts.get('bookmark'):
3993 if not revs:
3987 if not revs:
3994 revs = []
3988 revs = []
3995 # The list of bookmark used here is not the one used to actually
3989 # The list of bookmark used here is not the one used to actually
3996 # update the bookmark name. This can result in the revision pulled
3990 # update the bookmark name. This can result in the revision pulled
3997 # not ending up with the name of the bookmark because of a race
3991 # not ending up with the name of the bookmark because of a race
3998 # condition on the server. (See issue 4689 for details)
3992 # condition on the server. (See issue 4689 for details)
3999 remotebookmarks = other.listkeys('bookmarks')
3993 remotebookmarks = other.listkeys('bookmarks')
4000 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
3994 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4001 pullopargs['remotebookmarks'] = remotebookmarks
3995 pullopargs['remotebookmarks'] = remotebookmarks
4002 for b in opts['bookmark']:
3996 for b in opts['bookmark']:
4003 b = repo._bookmarks.expandname(b)
3997 b = repo._bookmarks.expandname(b)
4004 if b not in remotebookmarks:
3998 if b not in remotebookmarks:
4005 raise error.Abort(_('remote bookmark %s not found!') % b)
3999 raise error.Abort(_('remote bookmark %s not found!') % b)
4006 revs.append(hex(remotebookmarks[b]))
4000 revs.append(hex(remotebookmarks[b]))
4007
4001
4008 if revs:
4002 if revs:
4009 try:
4003 try:
4010 # When 'rev' is a bookmark name, we cannot guarantee that it
4004 # When 'rev' is a bookmark name, we cannot guarantee that it
4011 # will be updated with that name because of a race condition
4005 # will be updated with that name because of a race condition
4012 # server side. (See issue 4689 for details)
4006 # server side. (See issue 4689 for details)
4013 oldrevs = revs
4007 oldrevs = revs
4014 revs = [] # actually, nodes
4008 revs = [] # actually, nodes
4015 for r in oldrevs:
4009 for r in oldrevs:
4016 node = other.lookup(r)
4010 node = other.lookup(r)
4017 revs.append(node)
4011 revs.append(node)
4018 if r == checkout:
4012 if r == checkout:
4019 checkout = node
4013 checkout = node
4020 except error.CapabilityError:
4014 except error.CapabilityError:
4021 err = _("other repository doesn't support revision lookup, "
4015 err = _("other repository doesn't support revision lookup, "
4022 "so a rev cannot be specified.")
4016 "so a rev cannot be specified.")
4023 raise error.Abort(err)
4017 raise error.Abort(err)
4024
4018
4025 pullopargs.update(opts.get('opargs', {}))
4019 pullopargs.update(opts.get('opargs', {}))
4026 modheads = exchange.pull(repo, other, heads=revs,
4020 modheads = exchange.pull(repo, other, heads=revs,
4027 force=opts.get('force'),
4021 force=opts.get('force'),
4028 bookmarks=opts.get('bookmark', ()),
4022 bookmarks=opts.get('bookmark', ()),
4029 opargs=pullopargs).cgresult
4023 opargs=pullopargs).cgresult
4030
4024
4031 # brev is a name, which might be a bookmark to be activated at
4025 # brev is a name, which might be a bookmark to be activated at
4032 # the end of the update. In other words, it is an explicit
4026 # the end of the update. In other words, it is an explicit
4033 # destination of the update
4027 # destination of the update
4034 brev = None
4028 brev = None
4035
4029
4036 if checkout:
4030 if checkout:
4037 checkout = str(repo.changelog.rev(checkout))
4031 checkout = str(repo.changelog.rev(checkout))
4038
4032
4039 # order below depends on implementation of
4033 # order below depends on implementation of
4040 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4034 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4041 # because 'checkout' is determined without it.
4035 # because 'checkout' is determined without it.
4042 if opts.get('rev'):
4036 if opts.get('rev'):
4043 brev = opts['rev'][0]
4037 brev = opts['rev'][0]
4044 elif opts.get('branch'):
4038 elif opts.get('branch'):
4045 brev = opts['branch'][0]
4039 brev = opts['branch'][0]
4046 else:
4040 else:
4047 brev = branches[0]
4041 brev = branches[0]
4048 repo._subtoppath = source
4042 repo._subtoppath = source
4049 try:
4043 try:
4050 ret = postincoming(ui, repo, modheads, opts.get('update'),
4044 ret = postincoming(ui, repo, modheads, opts.get('update'),
4051 checkout, brev)
4045 checkout, brev)
4052
4046
4053 finally:
4047 finally:
4054 del repo._subtoppath
4048 del repo._subtoppath
4055
4049
4056 finally:
4050 finally:
4057 other.close()
4051 other.close()
4058 return ret
4052 return ret
4059
4053
4060 @command('^push',
4054 @command('^push',
4061 [('f', 'force', None, _('force push')),
4055 [('f', 'force', None, _('force push')),
4062 ('r', 'rev', [],
4056 ('r', 'rev', [],
4063 _('a changeset intended to be included in the destination'),
4057 _('a changeset intended to be included in the destination'),
4064 _('REV')),
4058 _('REV')),
4065 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4059 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4066 ('b', 'branch', [],
4060 ('b', 'branch', [],
4067 _('a specific branch you would like to push'), _('BRANCH')),
4061 _('a specific branch you would like to push'), _('BRANCH')),
4068 ('', 'new-branch', False, _('allow pushing a new branch')),
4062 ('', 'new-branch', False, _('allow pushing a new branch')),
4069 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4063 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4070 ] + remoteopts,
4064 ] + remoteopts,
4071 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4065 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4072 def push(ui, repo, dest=None, **opts):
4066 def push(ui, repo, dest=None, **opts):
4073 """push changes to the specified destination
4067 """push changes to the specified destination
4074
4068
4075 Push changesets from the local repository to the specified
4069 Push changesets from the local repository to the specified
4076 destination.
4070 destination.
4077
4071
4078 This operation is symmetrical to pull: it is identical to a pull
4072 This operation is symmetrical to pull: it is identical to a pull
4079 in the destination repository from the current one.
4073 in the destination repository from the current one.
4080
4074
4081 By default, push will not allow creation of new heads at the
4075 By default, push will not allow creation of new heads at the
4082 destination, since multiple heads would make it unclear which head
4076 destination, since multiple heads would make it unclear which head
4083 to use. In this situation, it is recommended to pull and merge
4077 to use. In this situation, it is recommended to pull and merge
4084 before pushing.
4078 before pushing.
4085
4079
4086 Use --new-branch if you want to allow push to create a new named
4080 Use --new-branch if you want to allow push to create a new named
4087 branch that is not present at the destination. This allows you to
4081 branch that is not present at the destination. This allows you to
4088 only create a new branch without forcing other changes.
4082 only create a new branch without forcing other changes.
4089
4083
4090 .. note::
4084 .. note::
4091
4085
4092 Extra care should be taken with the -f/--force option,
4086 Extra care should be taken with the -f/--force option,
4093 which will push all new heads on all branches, an action which will
4087 which will push all new heads on all branches, an action which will
4094 almost always cause confusion for collaborators.
4088 almost always cause confusion for collaborators.
4095
4089
4096 If -r/--rev is used, the specified revision and all its ancestors
4090 If -r/--rev is used, the specified revision and all its ancestors
4097 will be pushed to the remote repository.
4091 will be pushed to the remote repository.
4098
4092
4099 If -B/--bookmark is used, the specified bookmarked revision, its
4093 If -B/--bookmark is used, the specified bookmarked revision, its
4100 ancestors, and the bookmark will be pushed to the remote
4094 ancestors, and the bookmark will be pushed to the remote
4101 repository. Specifying ``.`` is equivalent to specifying the active
4095 repository. Specifying ``.`` is equivalent to specifying the active
4102 bookmark's name.
4096 bookmark's name.
4103
4097
4104 Please see :hg:`help urls` for important details about ``ssh://``
4098 Please see :hg:`help urls` for important details about ``ssh://``
4105 URLs. If DESTINATION is omitted, a default path will be used.
4099 URLs. If DESTINATION is omitted, a default path will be used.
4106
4100
4107 .. container:: verbose
4101 .. container:: verbose
4108
4102
4109 The --pushvars option sends strings to the server that become
4103 The --pushvars option sends strings to the server that become
4110 environment variables prepended with ``HG_USERVAR_``. For example,
4104 environment variables prepended with ``HG_USERVAR_``. For example,
4111 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4105 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4112 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4106 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4113
4107
4114 pushvars can provide for user-overridable hooks as well as set debug
4108 pushvars can provide for user-overridable hooks as well as set debug
4115 levels. One example is having a hook that blocks commits containing
4109 levels. One example is having a hook that blocks commits containing
4116 conflict markers, but enables the user to override the hook if the file
4110 conflict markers, but enables the user to override the hook if the file
4117 is using conflict markers for testing purposes or the file format has
4111 is using conflict markers for testing purposes or the file format has
4118 strings that look like conflict markers.
4112 strings that look like conflict markers.
4119
4113
4120 By default, servers will ignore `--pushvars`. To enable it add the
4114 By default, servers will ignore `--pushvars`. To enable it add the
4121 following to your configuration file::
4115 following to your configuration file::
4122
4116
4123 [push]
4117 [push]
4124 pushvars.server = true
4118 pushvars.server = true
4125
4119
4126 Returns 0 if push was successful, 1 if nothing to push.
4120 Returns 0 if push was successful, 1 if nothing to push.
4127 """
4121 """
4128
4122
4129 opts = pycompat.byteskwargs(opts)
4123 opts = pycompat.byteskwargs(opts)
4130 if opts.get('bookmark'):
4124 if opts.get('bookmark'):
4131 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4125 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4132 for b in opts['bookmark']:
4126 for b in opts['bookmark']:
4133 # translate -B options to -r so changesets get pushed
4127 # translate -B options to -r so changesets get pushed
4134 b = repo._bookmarks.expandname(b)
4128 b = repo._bookmarks.expandname(b)
4135 if b in repo._bookmarks:
4129 if b in repo._bookmarks:
4136 opts.setdefault('rev', []).append(b)
4130 opts.setdefault('rev', []).append(b)
4137 else:
4131 else:
4138 # if we try to push a deleted bookmark, translate it to null
4132 # if we try to push a deleted bookmark, translate it to null
4139 # this lets simultaneous -r, -b options continue working
4133 # this lets simultaneous -r, -b options continue working
4140 opts.setdefault('rev', []).append("null")
4134 opts.setdefault('rev', []).append("null")
4141
4135
4142 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4136 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4143 if not path:
4137 if not path:
4144 raise error.Abort(_('default repository not configured!'),
4138 raise error.Abort(_('default repository not configured!'),
4145 hint=_("see 'hg help config.paths'"))
4139 hint=_("see 'hg help config.paths'"))
4146 dest = path.pushloc or path.loc
4140 dest = path.pushloc or path.loc
4147 branches = (path.branch, opts.get('branch') or [])
4141 branches = (path.branch, opts.get('branch') or [])
4148 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4142 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4149 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4143 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4150 other = hg.peer(repo, opts, dest)
4144 other = hg.peer(repo, opts, dest)
4151
4145
4152 if revs:
4146 if revs:
4153 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4147 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4154 if not revs:
4148 if not revs:
4155 raise error.Abort(_("specified revisions evaluate to an empty set"),
4149 raise error.Abort(_("specified revisions evaluate to an empty set"),
4156 hint=_("use different revision arguments"))
4150 hint=_("use different revision arguments"))
4157 elif path.pushrev:
4151 elif path.pushrev:
4158 # It doesn't make any sense to specify ancestor revisions. So limit
4152 # It doesn't make any sense to specify ancestor revisions. So limit
4159 # to DAG heads to make discovery simpler.
4153 # to DAG heads to make discovery simpler.
4160 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4154 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4161 revs = scmutil.revrange(repo, [expr])
4155 revs = scmutil.revrange(repo, [expr])
4162 revs = [repo[rev].node() for rev in revs]
4156 revs = [repo[rev].node() for rev in revs]
4163 if not revs:
4157 if not revs:
4164 raise error.Abort(_('default push revset for path evaluates to an '
4158 raise error.Abort(_('default push revset for path evaluates to an '
4165 'empty set'))
4159 'empty set'))
4166
4160
4167 repo._subtoppath = dest
4161 repo._subtoppath = dest
4168 try:
4162 try:
4169 # push subrepos depth-first for coherent ordering
4163 # push subrepos depth-first for coherent ordering
4170 c = repo['']
4164 c = repo['']
4171 subs = c.substate # only repos that are committed
4165 subs = c.substate # only repos that are committed
4172 for s in sorted(subs):
4166 for s in sorted(subs):
4173 result = c.sub(s).push(opts)
4167 result = c.sub(s).push(opts)
4174 if result == 0:
4168 if result == 0:
4175 return not result
4169 return not result
4176 finally:
4170 finally:
4177 del repo._subtoppath
4171 del repo._subtoppath
4178
4172
4179 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4173 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4180 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4174 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4181
4175
4182 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4176 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4183 newbranch=opts.get('new_branch'),
4177 newbranch=opts.get('new_branch'),
4184 bookmarks=opts.get('bookmark', ()),
4178 bookmarks=opts.get('bookmark', ()),
4185 opargs=opargs)
4179 opargs=opargs)
4186
4180
4187 result = not pushop.cgresult
4181 result = not pushop.cgresult
4188
4182
4189 if pushop.bkresult is not None:
4183 if pushop.bkresult is not None:
4190 if pushop.bkresult == 2:
4184 if pushop.bkresult == 2:
4191 result = 2
4185 result = 2
4192 elif not result and pushop.bkresult:
4186 elif not result and pushop.bkresult:
4193 result = 2
4187 result = 2
4194
4188
4195 return result
4189 return result
4196
4190
4197 @command('recover', [])
4191 @command('recover', [])
4198 def recover(ui, repo):
4192 def recover(ui, repo):
4199 """roll back an interrupted transaction
4193 """roll back an interrupted transaction
4200
4194
4201 Recover from an interrupted commit or pull.
4195 Recover from an interrupted commit or pull.
4202
4196
4203 This command tries to fix the repository status after an
4197 This command tries to fix the repository status after an
4204 interrupted operation. It should only be necessary when Mercurial
4198 interrupted operation. It should only be necessary when Mercurial
4205 suggests it.
4199 suggests it.
4206
4200
4207 Returns 0 if successful, 1 if nothing to recover or verify fails.
4201 Returns 0 if successful, 1 if nothing to recover or verify fails.
4208 """
4202 """
4209 if repo.recover():
4203 if repo.recover():
4210 return hg.verify(repo)
4204 return hg.verify(repo)
4211 return 1
4205 return 1
4212
4206
4213 @command('^remove|rm',
4207 @command('^remove|rm',
4214 [('A', 'after', None, _('record delete for missing files')),
4208 [('A', 'after', None, _('record delete for missing files')),
4215 ('f', 'force', None,
4209 ('f', 'force', None,
4216 _('forget added files, delete modified files')),
4210 _('forget added files, delete modified files')),
4217 ] + subrepoopts + walkopts,
4211 ] + subrepoopts + walkopts,
4218 _('[OPTION]... FILE...'),
4212 _('[OPTION]... FILE...'),
4219 inferrepo=True)
4213 inferrepo=True)
4220 def remove(ui, repo, *pats, **opts):
4214 def remove(ui, repo, *pats, **opts):
4221 """remove the specified files on the next commit
4215 """remove the specified files on the next commit
4222
4216
4223 Schedule the indicated files for removal from the current branch.
4217 Schedule the indicated files for removal from the current branch.
4224
4218
4225 This command schedules the files to be removed at the next commit.
4219 This command schedules the files to be removed at the next commit.
4226 To undo a remove before that, see :hg:`revert`. To undo added
4220 To undo a remove before that, see :hg:`revert`. To undo added
4227 files, see :hg:`forget`.
4221 files, see :hg:`forget`.
4228
4222
4229 .. container:: verbose
4223 .. container:: verbose
4230
4224
4231 -A/--after can be used to remove only files that have already
4225 -A/--after can be used to remove only files that have already
4232 been deleted, -f/--force can be used to force deletion, and -Af
4226 been deleted, -f/--force can be used to force deletion, and -Af
4233 can be used to remove files from the next revision without
4227 can be used to remove files from the next revision without
4234 deleting them from the working directory.
4228 deleting them from the working directory.
4235
4229
4236 The following table details the behavior of remove for different
4230 The following table details the behavior of remove for different
4237 file states (columns) and option combinations (rows). The file
4231 file states (columns) and option combinations (rows). The file
4238 states are Added [A], Clean [C], Modified [M] and Missing [!]
4232 states are Added [A], Clean [C], Modified [M] and Missing [!]
4239 (as reported by :hg:`status`). The actions are Warn, Remove
4233 (as reported by :hg:`status`). The actions are Warn, Remove
4240 (from branch) and Delete (from disk):
4234 (from branch) and Delete (from disk):
4241
4235
4242 ========= == == == ==
4236 ========= == == == ==
4243 opt/state A C M !
4237 opt/state A C M !
4244 ========= == == == ==
4238 ========= == == == ==
4245 none W RD W R
4239 none W RD W R
4246 -f R RD RD R
4240 -f R RD RD R
4247 -A W W W R
4241 -A W W W R
4248 -Af R R R R
4242 -Af R R R R
4249 ========= == == == ==
4243 ========= == == == ==
4250
4244
4251 .. note::
4245 .. note::
4252
4246
4253 :hg:`remove` never deletes files in Added [A] state from the
4247 :hg:`remove` never deletes files in Added [A] state from the
4254 working directory, not even if ``--force`` is specified.
4248 working directory, not even if ``--force`` is specified.
4255
4249
4256 Returns 0 on success, 1 if any warnings encountered.
4250 Returns 0 on success, 1 if any warnings encountered.
4257 """
4251 """
4258
4252
4259 opts = pycompat.byteskwargs(opts)
4253 opts = pycompat.byteskwargs(opts)
4260 after, force = opts.get('after'), opts.get('force')
4254 after, force = opts.get('after'), opts.get('force')
4261 if not pats and not after:
4255 if not pats and not after:
4262 raise error.Abort(_('no files specified'))
4256 raise error.Abort(_('no files specified'))
4263
4257
4264 m = scmutil.match(repo[None], pats, opts)
4258 m = scmutil.match(repo[None], pats, opts)
4265 subrepos = opts.get('subrepos')
4259 subrepos = opts.get('subrepos')
4266 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
4260 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
4267
4261
4268 @command('rename|move|mv',
4262 @command('rename|move|mv',
4269 [('A', 'after', None, _('record a rename that has already occurred')),
4263 [('A', 'after', None, _('record a rename that has already occurred')),
4270 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4264 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4271 ] + walkopts + dryrunopts,
4265 ] + walkopts + dryrunopts,
4272 _('[OPTION]... SOURCE... DEST'))
4266 _('[OPTION]... SOURCE... DEST'))
4273 def rename(ui, repo, *pats, **opts):
4267 def rename(ui, repo, *pats, **opts):
4274 """rename files; equivalent of copy + remove
4268 """rename files; equivalent of copy + remove
4275
4269
4276 Mark dest as copies of sources; mark sources for deletion. If dest
4270 Mark dest as copies of sources; mark sources for deletion. If dest
4277 is a directory, copies are put in that directory. If dest is a
4271 is a directory, copies are put in that directory. If dest is a
4278 file, there can only be one source.
4272 file, there can only be one source.
4279
4273
4280 By default, this command copies the contents of files as they
4274 By default, this command copies the contents of files as they
4281 exist in the working directory. If invoked with -A/--after, the
4275 exist in the working directory. If invoked with -A/--after, the
4282 operation is recorded, but no copying is performed.
4276 operation is recorded, but no copying is performed.
4283
4277
4284 This command takes effect at the next commit. To undo a rename
4278 This command takes effect at the next commit. To undo a rename
4285 before that, see :hg:`revert`.
4279 before that, see :hg:`revert`.
4286
4280
4287 Returns 0 on success, 1 if errors are encountered.
4281 Returns 0 on success, 1 if errors are encountered.
4288 """
4282 """
4289 opts = pycompat.byteskwargs(opts)
4283 opts = pycompat.byteskwargs(opts)
4290 with repo.wlock(False):
4284 with repo.wlock(False):
4291 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4285 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4292
4286
4293 @command('resolve',
4287 @command('resolve',
4294 [('a', 'all', None, _('select all unresolved files')),
4288 [('a', 'all', None, _('select all unresolved files')),
4295 ('l', 'list', None, _('list state of files needing merge')),
4289 ('l', 'list', None, _('list state of files needing merge')),
4296 ('m', 'mark', None, _('mark files as resolved')),
4290 ('m', 'mark', None, _('mark files as resolved')),
4297 ('u', 'unmark', None, _('mark files as unresolved')),
4291 ('u', 'unmark', None, _('mark files as unresolved')),
4298 ('n', 'no-status', None, _('hide status prefix'))]
4292 ('n', 'no-status', None, _('hide status prefix'))]
4299 + mergetoolopts + walkopts + formatteropts,
4293 + mergetoolopts + walkopts + formatteropts,
4300 _('[OPTION]... [FILE]...'),
4294 _('[OPTION]... [FILE]...'),
4301 inferrepo=True)
4295 inferrepo=True)
4302 def resolve(ui, repo, *pats, **opts):
4296 def resolve(ui, repo, *pats, **opts):
4303 """redo merges or set/view the merge status of files
4297 """redo merges or set/view the merge status of files
4304
4298
4305 Merges with unresolved conflicts are often the result of
4299 Merges with unresolved conflicts are often the result of
4306 non-interactive merging using the ``internal:merge`` configuration
4300 non-interactive merging using the ``internal:merge`` configuration
4307 setting, or a command-line merge tool like ``diff3``. The resolve
4301 setting, or a command-line merge tool like ``diff3``. The resolve
4308 command is used to manage the files involved in a merge, after
4302 command is used to manage the files involved in a merge, after
4309 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4303 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4310 working directory must have two parents). See :hg:`help
4304 working directory must have two parents). See :hg:`help
4311 merge-tools` for information on configuring merge tools.
4305 merge-tools` for information on configuring merge tools.
4312
4306
4313 The resolve command can be used in the following ways:
4307 The resolve command can be used in the following ways:
4314
4308
4315 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4309 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4316 files, discarding any previous merge attempts. Re-merging is not
4310 files, discarding any previous merge attempts. Re-merging is not
4317 performed for files already marked as resolved. Use ``--all/-a``
4311 performed for files already marked as resolved. Use ``--all/-a``
4318 to select all unresolved files. ``--tool`` can be used to specify
4312 to select all unresolved files. ``--tool`` can be used to specify
4319 the merge tool used for the given files. It overrides the HGMERGE
4313 the merge tool used for the given files. It overrides the HGMERGE
4320 environment variable and your configuration files. Previous file
4314 environment variable and your configuration files. Previous file
4321 contents are saved with a ``.orig`` suffix.
4315 contents are saved with a ``.orig`` suffix.
4322
4316
4323 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4317 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4324 (e.g. after having manually fixed-up the files). The default is
4318 (e.g. after having manually fixed-up the files). The default is
4325 to mark all unresolved files.
4319 to mark all unresolved files.
4326
4320
4327 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4321 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4328 default is to mark all resolved files.
4322 default is to mark all resolved files.
4329
4323
4330 - :hg:`resolve -l`: list files which had or still have conflicts.
4324 - :hg:`resolve -l`: list files which had or still have conflicts.
4331 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4325 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4332 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4326 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4333 the list. See :hg:`help filesets` for details.
4327 the list. See :hg:`help filesets` for details.
4334
4328
4335 .. note::
4329 .. note::
4336
4330
4337 Mercurial will not let you commit files with unresolved merge
4331 Mercurial will not let you commit files with unresolved merge
4338 conflicts. You must use :hg:`resolve -m ...` before you can
4332 conflicts. You must use :hg:`resolve -m ...` before you can
4339 commit after a conflicting merge.
4333 commit after a conflicting merge.
4340
4334
4341 Returns 0 on success, 1 if any files fail a resolve attempt.
4335 Returns 0 on success, 1 if any files fail a resolve attempt.
4342 """
4336 """
4343
4337
4344 opts = pycompat.byteskwargs(opts)
4338 opts = pycompat.byteskwargs(opts)
4345 flaglist = 'all mark unmark list no_status'.split()
4339 flaglist = 'all mark unmark list no_status'.split()
4346 all, mark, unmark, show, nostatus = \
4340 all, mark, unmark, show, nostatus = \
4347 [opts.get(o) for o in flaglist]
4341 [opts.get(o) for o in flaglist]
4348
4342
4349 if (show and (mark or unmark)) or (mark and unmark):
4343 if (show and (mark or unmark)) or (mark and unmark):
4350 raise error.Abort(_("too many options specified"))
4344 raise error.Abort(_("too many options specified"))
4351 if pats and all:
4345 if pats and all:
4352 raise error.Abort(_("can't specify --all and patterns"))
4346 raise error.Abort(_("can't specify --all and patterns"))
4353 if not (all or pats or show or mark or unmark):
4347 if not (all or pats or show or mark or unmark):
4354 raise error.Abort(_('no files or directories specified'),
4348 raise error.Abort(_('no files or directories specified'),
4355 hint=('use --all to re-merge all unresolved files'))
4349 hint=('use --all to re-merge all unresolved files'))
4356
4350
4357 if show:
4351 if show:
4358 ui.pager('resolve')
4352 ui.pager('resolve')
4359 fm = ui.formatter('resolve', opts)
4353 fm = ui.formatter('resolve', opts)
4360 ms = mergemod.mergestate.read(repo)
4354 ms = mergemod.mergestate.read(repo)
4361 m = scmutil.match(repo[None], pats, opts)
4355 m = scmutil.match(repo[None], pats, opts)
4362
4356
4363 # Labels and keys based on merge state. Unresolved path conflicts show
4357 # Labels and keys based on merge state. Unresolved path conflicts show
4364 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4358 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4365 # resolved conflicts.
4359 # resolved conflicts.
4366 mergestateinfo = {
4360 mergestateinfo = {
4367 'u': ('resolve.unresolved', 'U'),
4361 'u': ('resolve.unresolved', 'U'),
4368 'r': ('resolve.resolved', 'R'),
4362 'r': ('resolve.resolved', 'R'),
4369 'pu': ('resolve.unresolved', 'P'),
4363 'pu': ('resolve.unresolved', 'P'),
4370 'pr': ('resolve.resolved', 'R'),
4364 'pr': ('resolve.resolved', 'R'),
4371 'd': ('resolve.driverresolved', 'D'),
4365 'd': ('resolve.driverresolved', 'D'),
4372 }
4366 }
4373
4367
4374 for f in ms:
4368 for f in ms:
4375 if not m(f):
4369 if not m(f):
4376 continue
4370 continue
4377
4371
4378 label, key = mergestateinfo[ms[f]]
4372 label, key = mergestateinfo[ms[f]]
4379 fm.startitem()
4373 fm.startitem()
4380 fm.condwrite(not nostatus, 'status', '%s ', key, label=label)
4374 fm.condwrite(not nostatus, 'status', '%s ', key, label=label)
4381 fm.write('path', '%s\n', f, label=label)
4375 fm.write('path', '%s\n', f, label=label)
4382 fm.end()
4376 fm.end()
4383 return 0
4377 return 0
4384
4378
4385 with repo.wlock():
4379 with repo.wlock():
4386 ms = mergemod.mergestate.read(repo)
4380 ms = mergemod.mergestate.read(repo)
4387
4381
4388 if not (ms.active() or repo.dirstate.p2() != nullid):
4382 if not (ms.active() or repo.dirstate.p2() != nullid):
4389 raise error.Abort(
4383 raise error.Abort(
4390 _('resolve command not applicable when not merging'))
4384 _('resolve command not applicable when not merging'))
4391
4385
4392 wctx = repo[None]
4386 wctx = repo[None]
4393
4387
4394 if ms.mergedriver and ms.mdstate() == 'u':
4388 if ms.mergedriver and ms.mdstate() == 'u':
4395 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4389 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4396 ms.commit()
4390 ms.commit()
4397 # allow mark and unmark to go through
4391 # allow mark and unmark to go through
4398 if not mark and not unmark and not proceed:
4392 if not mark and not unmark and not proceed:
4399 return 1
4393 return 1
4400
4394
4401 m = scmutil.match(wctx, pats, opts)
4395 m = scmutil.match(wctx, pats, opts)
4402 ret = 0
4396 ret = 0
4403 didwork = False
4397 didwork = False
4404 runconclude = False
4398 runconclude = False
4405
4399
4406 tocomplete = []
4400 tocomplete = []
4407 for f in ms:
4401 for f in ms:
4408 if not m(f):
4402 if not m(f):
4409 continue
4403 continue
4410
4404
4411 didwork = True
4405 didwork = True
4412
4406
4413 # don't let driver-resolved files be marked, and run the conclude
4407 # don't let driver-resolved files be marked, and run the conclude
4414 # step if asked to resolve
4408 # step if asked to resolve
4415 if ms[f] == "d":
4409 if ms[f] == "d":
4416 exact = m.exact(f)
4410 exact = m.exact(f)
4417 if mark:
4411 if mark:
4418 if exact:
4412 if exact:
4419 ui.warn(_('not marking %s as it is driver-resolved\n')
4413 ui.warn(_('not marking %s as it is driver-resolved\n')
4420 % f)
4414 % f)
4421 elif unmark:
4415 elif unmark:
4422 if exact:
4416 if exact:
4423 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4417 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4424 % f)
4418 % f)
4425 else:
4419 else:
4426 runconclude = True
4420 runconclude = True
4427 continue
4421 continue
4428
4422
4429 # path conflicts must be resolved manually
4423 # path conflicts must be resolved manually
4430 if ms[f] in ("pu", "pr"):
4424 if ms[f] in ("pu", "pr"):
4431 if mark:
4425 if mark:
4432 ms.mark(f, "pr")
4426 ms.mark(f, "pr")
4433 elif unmark:
4427 elif unmark:
4434 ms.mark(f, "pu")
4428 ms.mark(f, "pu")
4435 elif ms[f] == "pu":
4429 elif ms[f] == "pu":
4436 ui.warn(_('%s: path conflict must be resolved manually\n')
4430 ui.warn(_('%s: path conflict must be resolved manually\n')
4437 % f)
4431 % f)
4438 continue
4432 continue
4439
4433
4440 if mark:
4434 if mark:
4441 ms.mark(f, "r")
4435 ms.mark(f, "r")
4442 elif unmark:
4436 elif unmark:
4443 ms.mark(f, "u")
4437 ms.mark(f, "u")
4444 else:
4438 else:
4445 # backup pre-resolve (merge uses .orig for its own purposes)
4439 # backup pre-resolve (merge uses .orig for its own purposes)
4446 a = repo.wjoin(f)
4440 a = repo.wjoin(f)
4447 try:
4441 try:
4448 util.copyfile(a, a + ".resolve")
4442 util.copyfile(a, a + ".resolve")
4449 except (IOError, OSError) as inst:
4443 except (IOError, OSError) as inst:
4450 if inst.errno != errno.ENOENT:
4444 if inst.errno != errno.ENOENT:
4451 raise
4445 raise
4452
4446
4453 try:
4447 try:
4454 # preresolve file
4448 # preresolve file
4455 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4449 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4456 'resolve')
4450 'resolve')
4457 complete, r = ms.preresolve(f, wctx)
4451 complete, r = ms.preresolve(f, wctx)
4458 if not complete:
4452 if not complete:
4459 tocomplete.append(f)
4453 tocomplete.append(f)
4460 elif r:
4454 elif r:
4461 ret = 1
4455 ret = 1
4462 finally:
4456 finally:
4463 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4457 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4464 ms.commit()
4458 ms.commit()
4465
4459
4466 # replace filemerge's .orig file with our resolve file, but only
4460 # replace filemerge's .orig file with our resolve file, but only
4467 # for merges that are complete
4461 # for merges that are complete
4468 if complete:
4462 if complete:
4469 try:
4463 try:
4470 util.rename(a + ".resolve",
4464 util.rename(a + ".resolve",
4471 scmutil.origpath(ui, repo, a))
4465 scmutil.origpath(ui, repo, a))
4472 except OSError as inst:
4466 except OSError as inst:
4473 if inst.errno != errno.ENOENT:
4467 if inst.errno != errno.ENOENT:
4474 raise
4468 raise
4475
4469
4476 for f in tocomplete:
4470 for f in tocomplete:
4477 try:
4471 try:
4478 # resolve file
4472 # resolve file
4479 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4473 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4480 'resolve')
4474 'resolve')
4481 r = ms.resolve(f, wctx)
4475 r = ms.resolve(f, wctx)
4482 if r:
4476 if r:
4483 ret = 1
4477 ret = 1
4484 finally:
4478 finally:
4485 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4479 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4486 ms.commit()
4480 ms.commit()
4487
4481
4488 # replace filemerge's .orig file with our resolve file
4482 # replace filemerge's .orig file with our resolve file
4489 a = repo.wjoin(f)
4483 a = repo.wjoin(f)
4490 try:
4484 try:
4491 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4485 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4492 except OSError as inst:
4486 except OSError as inst:
4493 if inst.errno != errno.ENOENT:
4487 if inst.errno != errno.ENOENT:
4494 raise
4488 raise
4495
4489
4496 ms.commit()
4490 ms.commit()
4497 ms.recordactions()
4491 ms.recordactions()
4498
4492
4499 if not didwork and pats:
4493 if not didwork and pats:
4500 hint = None
4494 hint = None
4501 if not any([p for p in pats if p.find(':') >= 0]):
4495 if not any([p for p in pats if p.find(':') >= 0]):
4502 pats = ['path:%s' % p for p in pats]
4496 pats = ['path:%s' % p for p in pats]
4503 m = scmutil.match(wctx, pats, opts)
4497 m = scmutil.match(wctx, pats, opts)
4504 for f in ms:
4498 for f in ms:
4505 if not m(f):
4499 if not m(f):
4506 continue
4500 continue
4507 flags = ''.join(['-%s ' % o[0] for o in flaglist
4501 flags = ''.join(['-%s ' % o[0] for o in flaglist
4508 if opts.get(o)])
4502 if opts.get(o)])
4509 hint = _("(try: hg resolve %s%s)\n") % (
4503 hint = _("(try: hg resolve %s%s)\n") % (
4510 flags,
4504 flags,
4511 ' '.join(pats))
4505 ' '.join(pats))
4512 break
4506 break
4513 ui.warn(_("arguments do not match paths that need resolving\n"))
4507 ui.warn(_("arguments do not match paths that need resolving\n"))
4514 if hint:
4508 if hint:
4515 ui.warn(hint)
4509 ui.warn(hint)
4516 elif ms.mergedriver and ms.mdstate() != 's':
4510 elif ms.mergedriver and ms.mdstate() != 's':
4517 # run conclude step when either a driver-resolved file is requested
4511 # run conclude step when either a driver-resolved file is requested
4518 # or there are no driver-resolved files
4512 # or there are no driver-resolved files
4519 # we can't use 'ret' to determine whether any files are unresolved
4513 # we can't use 'ret' to determine whether any files are unresolved
4520 # because we might not have tried to resolve some
4514 # because we might not have tried to resolve some
4521 if ((runconclude or not list(ms.driverresolved()))
4515 if ((runconclude or not list(ms.driverresolved()))
4522 and not list(ms.unresolved())):
4516 and not list(ms.unresolved())):
4523 proceed = mergemod.driverconclude(repo, ms, wctx)
4517 proceed = mergemod.driverconclude(repo, ms, wctx)
4524 ms.commit()
4518 ms.commit()
4525 if not proceed:
4519 if not proceed:
4526 return 1
4520 return 1
4527
4521
4528 # Nudge users into finishing an unfinished operation
4522 # Nudge users into finishing an unfinished operation
4529 unresolvedf = list(ms.unresolved())
4523 unresolvedf = list(ms.unresolved())
4530 driverresolvedf = list(ms.driverresolved())
4524 driverresolvedf = list(ms.driverresolved())
4531 if not unresolvedf and not driverresolvedf:
4525 if not unresolvedf and not driverresolvedf:
4532 ui.status(_('(no more unresolved files)\n'))
4526 ui.status(_('(no more unresolved files)\n'))
4533 cmdutil.checkafterresolved(repo)
4527 cmdutil.checkafterresolved(repo)
4534 elif not unresolvedf:
4528 elif not unresolvedf:
4535 ui.status(_('(no more unresolved files -- '
4529 ui.status(_('(no more unresolved files -- '
4536 'run "hg resolve --all" to conclude)\n'))
4530 'run "hg resolve --all" to conclude)\n'))
4537
4531
4538 return ret
4532 return ret
4539
4533
4540 @command('revert',
4534 @command('revert',
4541 [('a', 'all', None, _('revert all changes when no arguments given')),
4535 [('a', 'all', None, _('revert all changes when no arguments given')),
4542 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4536 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4543 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4537 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4544 ('C', 'no-backup', None, _('do not save backup copies of files')),
4538 ('C', 'no-backup', None, _('do not save backup copies of files')),
4545 ('i', 'interactive', None, _('interactively select the changes')),
4539 ('i', 'interactive', None, _('interactively select the changes')),
4546 ] + walkopts + dryrunopts,
4540 ] + walkopts + dryrunopts,
4547 _('[OPTION]... [-r REV] [NAME]...'))
4541 _('[OPTION]... [-r REV] [NAME]...'))
4548 def revert(ui, repo, *pats, **opts):
4542 def revert(ui, repo, *pats, **opts):
4549 """restore files to their checkout state
4543 """restore files to their checkout state
4550
4544
4551 .. note::
4545 .. note::
4552
4546
4553 To check out earlier revisions, you should use :hg:`update REV`.
4547 To check out earlier revisions, you should use :hg:`update REV`.
4554 To cancel an uncommitted merge (and lose your changes),
4548 To cancel an uncommitted merge (and lose your changes),
4555 use :hg:`update --clean .`.
4549 use :hg:`update --clean .`.
4556
4550
4557 With no revision specified, revert the specified files or directories
4551 With no revision specified, revert the specified files or directories
4558 to the contents they had in the parent of the working directory.
4552 to the contents they had in the parent of the working directory.
4559 This restores the contents of files to an unmodified
4553 This restores the contents of files to an unmodified
4560 state and unschedules adds, removes, copies, and renames. If the
4554 state and unschedules adds, removes, copies, and renames. If the
4561 working directory has two parents, you must explicitly specify a
4555 working directory has two parents, you must explicitly specify a
4562 revision.
4556 revision.
4563
4557
4564 Using the -r/--rev or -d/--date options, revert the given files or
4558 Using the -r/--rev or -d/--date options, revert the given files or
4565 directories to their states as of a specific revision. Because
4559 directories to their states as of a specific revision. Because
4566 revert does not change the working directory parents, this will
4560 revert does not change the working directory parents, this will
4567 cause these files to appear modified. This can be helpful to "back
4561 cause these files to appear modified. This can be helpful to "back
4568 out" some or all of an earlier change. See :hg:`backout` for a
4562 out" some or all of an earlier change. See :hg:`backout` for a
4569 related method.
4563 related method.
4570
4564
4571 Modified files are saved with a .orig suffix before reverting.
4565 Modified files are saved with a .orig suffix before reverting.
4572 To disable these backups, use --no-backup. It is possible to store
4566 To disable these backups, use --no-backup. It is possible to store
4573 the backup files in a custom directory relative to the root of the
4567 the backup files in a custom directory relative to the root of the
4574 repository by setting the ``ui.origbackuppath`` configuration
4568 repository by setting the ``ui.origbackuppath`` configuration
4575 option.
4569 option.
4576
4570
4577 See :hg:`help dates` for a list of formats valid for -d/--date.
4571 See :hg:`help dates` for a list of formats valid for -d/--date.
4578
4572
4579 See :hg:`help backout` for a way to reverse the effect of an
4573 See :hg:`help backout` for a way to reverse the effect of an
4580 earlier changeset.
4574 earlier changeset.
4581
4575
4582 Returns 0 on success.
4576 Returns 0 on success.
4583 """
4577 """
4584
4578
4585 opts = pycompat.byteskwargs(opts)
4579 opts = pycompat.byteskwargs(opts)
4586 if opts.get("date"):
4580 if opts.get("date"):
4587 if opts.get("rev"):
4581 if opts.get("rev"):
4588 raise error.Abort(_("you can't specify a revision and a date"))
4582 raise error.Abort(_("you can't specify a revision and a date"))
4589 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4583 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4590
4584
4591 parent, p2 = repo.dirstate.parents()
4585 parent, p2 = repo.dirstate.parents()
4592 if not opts.get('rev') and p2 != nullid:
4586 if not opts.get('rev') and p2 != nullid:
4593 # revert after merge is a trap for new users (issue2915)
4587 # revert after merge is a trap for new users (issue2915)
4594 raise error.Abort(_('uncommitted merge with no revision specified'),
4588 raise error.Abort(_('uncommitted merge with no revision specified'),
4595 hint=_("use 'hg update' or see 'hg help revert'"))
4589 hint=_("use 'hg update' or see 'hg help revert'"))
4596
4590
4597 rev = opts.get('rev')
4591 rev = opts.get('rev')
4598 if rev:
4592 if rev:
4599 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4593 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4600 ctx = scmutil.revsingle(repo, rev)
4594 ctx = scmutil.revsingle(repo, rev)
4601
4595
4602 if (not (pats or opts.get('include') or opts.get('exclude') or
4596 if (not (pats or opts.get('include') or opts.get('exclude') or
4603 opts.get('all') or opts.get('interactive'))):
4597 opts.get('all') or opts.get('interactive'))):
4604 msg = _("no files or directories specified")
4598 msg = _("no files or directories specified")
4605 if p2 != nullid:
4599 if p2 != nullid:
4606 hint = _("uncommitted merge, use --all to discard all changes,"
4600 hint = _("uncommitted merge, use --all to discard all changes,"
4607 " or 'hg update -C .' to abort the merge")
4601 " or 'hg update -C .' to abort the merge")
4608 raise error.Abort(msg, hint=hint)
4602 raise error.Abort(msg, hint=hint)
4609 dirty = any(repo.status())
4603 dirty = any(repo.status())
4610 node = ctx.node()
4604 node = ctx.node()
4611 if node != parent:
4605 if node != parent:
4612 if dirty:
4606 if dirty:
4613 hint = _("uncommitted changes, use --all to discard all"
4607 hint = _("uncommitted changes, use --all to discard all"
4614 " changes, or 'hg update %s' to update") % ctx.rev()
4608 " changes, or 'hg update %s' to update") % ctx.rev()
4615 else:
4609 else:
4616 hint = _("use --all to revert all files,"
4610 hint = _("use --all to revert all files,"
4617 " or 'hg update %s' to update") % ctx.rev()
4611 " or 'hg update %s' to update") % ctx.rev()
4618 elif dirty:
4612 elif dirty:
4619 hint = _("uncommitted changes, use --all to discard all changes")
4613 hint = _("uncommitted changes, use --all to discard all changes")
4620 else:
4614 else:
4621 hint = _("use --all to revert all files")
4615 hint = _("use --all to revert all files")
4622 raise error.Abort(msg, hint=hint)
4616 raise error.Abort(msg, hint=hint)
4623
4617
4624 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
4618 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
4625 **pycompat.strkwargs(opts))
4619 **pycompat.strkwargs(opts))
4626
4620
4627 @command('rollback', dryrunopts +
4621 @command('rollback', dryrunopts +
4628 [('f', 'force', False, _('ignore safety measures'))])
4622 [('f', 'force', False, _('ignore safety measures'))])
4629 def rollback(ui, repo, **opts):
4623 def rollback(ui, repo, **opts):
4630 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4624 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4631
4625
4632 Please use :hg:`commit --amend` instead of rollback to correct
4626 Please use :hg:`commit --amend` instead of rollback to correct
4633 mistakes in the last commit.
4627 mistakes in the last commit.
4634
4628
4635 This command should be used with care. There is only one level of
4629 This command should be used with care. There is only one level of
4636 rollback, and there is no way to undo a rollback. It will also
4630 rollback, and there is no way to undo a rollback. It will also
4637 restore the dirstate at the time of the last transaction, losing
4631 restore the dirstate at the time of the last transaction, losing
4638 any dirstate changes since that time. This command does not alter
4632 any dirstate changes since that time. This command does not alter
4639 the working directory.
4633 the working directory.
4640
4634
4641 Transactions are used to encapsulate the effects of all commands
4635 Transactions are used to encapsulate the effects of all commands
4642 that create new changesets or propagate existing changesets into a
4636 that create new changesets or propagate existing changesets into a
4643 repository.
4637 repository.
4644
4638
4645 .. container:: verbose
4639 .. container:: verbose
4646
4640
4647 For example, the following commands are transactional, and their
4641 For example, the following commands are transactional, and their
4648 effects can be rolled back:
4642 effects can be rolled back:
4649
4643
4650 - commit
4644 - commit
4651 - import
4645 - import
4652 - pull
4646 - pull
4653 - push (with this repository as the destination)
4647 - push (with this repository as the destination)
4654 - unbundle
4648 - unbundle
4655
4649
4656 To avoid permanent data loss, rollback will refuse to rollback a
4650 To avoid permanent data loss, rollback will refuse to rollback a
4657 commit transaction if it isn't checked out. Use --force to
4651 commit transaction if it isn't checked out. Use --force to
4658 override this protection.
4652 override this protection.
4659
4653
4660 The rollback command can be entirely disabled by setting the
4654 The rollback command can be entirely disabled by setting the
4661 ``ui.rollback`` configuration setting to false. If you're here
4655 ``ui.rollback`` configuration setting to false. If you're here
4662 because you want to use rollback and it's disabled, you can
4656 because you want to use rollback and it's disabled, you can
4663 re-enable the command by setting ``ui.rollback`` to true.
4657 re-enable the command by setting ``ui.rollback`` to true.
4664
4658
4665 This command is not intended for use on public repositories. Once
4659 This command is not intended for use on public repositories. Once
4666 changes are visible for pull by other users, rolling a transaction
4660 changes are visible for pull by other users, rolling a transaction
4667 back locally is ineffective (someone else may already have pulled
4661 back locally is ineffective (someone else may already have pulled
4668 the changes). Furthermore, a race is possible with readers of the
4662 the changes). Furthermore, a race is possible with readers of the
4669 repository; for example an in-progress pull from the repository
4663 repository; for example an in-progress pull from the repository
4670 may fail if a rollback is performed.
4664 may fail if a rollback is performed.
4671
4665
4672 Returns 0 on success, 1 if no rollback data is available.
4666 Returns 0 on success, 1 if no rollback data is available.
4673 """
4667 """
4674 if not ui.configbool('ui', 'rollback'):
4668 if not ui.configbool('ui', 'rollback'):
4675 raise error.Abort(_('rollback is disabled because it is unsafe'),
4669 raise error.Abort(_('rollback is disabled because it is unsafe'),
4676 hint=('see `hg help -v rollback` for information'))
4670 hint=('see `hg help -v rollback` for information'))
4677 return repo.rollback(dryrun=opts.get(r'dry_run'),
4671 return repo.rollback(dryrun=opts.get(r'dry_run'),
4678 force=opts.get(r'force'))
4672 force=opts.get(r'force'))
4679
4673
4680 @command('root', [], cmdtype=readonly)
4674 @command('root', [], cmdtype=readonly)
4681 def root(ui, repo):
4675 def root(ui, repo):
4682 """print the root (top) of the current working directory
4676 """print the root (top) of the current working directory
4683
4677
4684 Print the root directory of the current repository.
4678 Print the root directory of the current repository.
4685
4679
4686 Returns 0 on success.
4680 Returns 0 on success.
4687 """
4681 """
4688 ui.write(repo.root + "\n")
4682 ui.write(repo.root + "\n")
4689
4683
4690 @command('^serve',
4684 @command('^serve',
4691 [('A', 'accesslog', '', _('name of access log file to write to'),
4685 [('A', 'accesslog', '', _('name of access log file to write to'),
4692 _('FILE')),
4686 _('FILE')),
4693 ('d', 'daemon', None, _('run server in background')),
4687 ('d', 'daemon', None, _('run server in background')),
4694 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4688 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4695 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4689 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4696 # use string type, then we can check if something was passed
4690 # use string type, then we can check if something was passed
4697 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4691 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4698 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4692 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4699 _('ADDR')),
4693 _('ADDR')),
4700 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4694 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4701 _('PREFIX')),
4695 _('PREFIX')),
4702 ('n', 'name', '',
4696 ('n', 'name', '',
4703 _('name to show in web pages (default: working directory)'), _('NAME')),
4697 _('name to show in web pages (default: working directory)'), _('NAME')),
4704 ('', 'web-conf', '',
4698 ('', 'web-conf', '',
4705 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4699 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4706 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4700 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4707 _('FILE')),
4701 _('FILE')),
4708 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4702 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4709 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4703 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4710 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4704 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4711 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4705 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4712 ('', 'style', '', _('template style to use'), _('STYLE')),
4706 ('', 'style', '', _('template style to use'), _('STYLE')),
4713 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4707 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4714 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))]
4708 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))]
4715 + subrepoopts,
4709 + subrepoopts,
4716 _('[OPTION]...'),
4710 _('[OPTION]...'),
4717 optionalrepo=True)
4711 optionalrepo=True)
4718 def serve(ui, repo, **opts):
4712 def serve(ui, repo, **opts):
4719 """start stand-alone webserver
4713 """start stand-alone webserver
4720
4714
4721 Start a local HTTP repository browser and pull server. You can use
4715 Start a local HTTP repository browser and pull server. You can use
4722 this for ad-hoc sharing and browsing of repositories. It is
4716 this for ad-hoc sharing and browsing of repositories. It is
4723 recommended to use a real web server to serve a repository for
4717 recommended to use a real web server to serve a repository for
4724 longer periods of time.
4718 longer periods of time.
4725
4719
4726 Please note that the server does not implement access control.
4720 Please note that the server does not implement access control.
4727 This means that, by default, anybody can read from the server and
4721 This means that, by default, anybody can read from the server and
4728 nobody can write to it by default. Set the ``web.allow-push``
4722 nobody can write to it by default. Set the ``web.allow-push``
4729 option to ``*`` to allow everybody to push to the server. You
4723 option to ``*`` to allow everybody to push to the server. You
4730 should use a real web server if you need to authenticate users.
4724 should use a real web server if you need to authenticate users.
4731
4725
4732 By default, the server logs accesses to stdout and errors to
4726 By default, the server logs accesses to stdout and errors to
4733 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4727 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4734 files.
4728 files.
4735
4729
4736 To have the server choose a free port number to listen on, specify
4730 To have the server choose a free port number to listen on, specify
4737 a port number of 0; in this case, the server will print the port
4731 a port number of 0; in this case, the server will print the port
4738 number it uses.
4732 number it uses.
4739
4733
4740 Returns 0 on success.
4734 Returns 0 on success.
4741 """
4735 """
4742
4736
4743 opts = pycompat.byteskwargs(opts)
4737 opts = pycompat.byteskwargs(opts)
4744 if opts["stdio"] and opts["cmdserver"]:
4738 if opts["stdio"] and opts["cmdserver"]:
4745 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4739 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4746
4740
4747 if opts["stdio"]:
4741 if opts["stdio"]:
4748 if repo is None:
4742 if repo is None:
4749 raise error.RepoError(_("there is no Mercurial repository here"
4743 raise error.RepoError(_("there is no Mercurial repository here"
4750 " (.hg not found)"))
4744 " (.hg not found)"))
4751 s = sshserver.sshserver(ui, repo)
4745 s = sshserver.sshserver(ui, repo)
4752 s.serve_forever()
4746 s.serve_forever()
4753
4747
4754 service = server.createservice(ui, repo, opts)
4748 service = server.createservice(ui, repo, opts)
4755 return server.runservice(opts, initfn=service.init, runfn=service.run)
4749 return server.runservice(opts, initfn=service.init, runfn=service.run)
4756
4750
4757 @command('^status|st',
4751 @command('^status|st',
4758 [('A', 'all', None, _('show status of all files')),
4752 [('A', 'all', None, _('show status of all files')),
4759 ('m', 'modified', None, _('show only modified files')),
4753 ('m', 'modified', None, _('show only modified files')),
4760 ('a', 'added', None, _('show only added files')),
4754 ('a', 'added', None, _('show only added files')),
4761 ('r', 'removed', None, _('show only removed files')),
4755 ('r', 'removed', None, _('show only removed files')),
4762 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4756 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4763 ('c', 'clean', None, _('show only files without changes')),
4757 ('c', 'clean', None, _('show only files without changes')),
4764 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4758 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4765 ('i', 'ignored', None, _('show only ignored files')),
4759 ('i', 'ignored', None, _('show only ignored files')),
4766 ('n', 'no-status', None, _('hide status prefix')),
4760 ('n', 'no-status', None, _('hide status prefix')),
4767 ('t', 'terse', '', _('show the terse output (EXPERIMENTAL)')),
4761 ('t', 'terse', '', _('show the terse output (EXPERIMENTAL)')),
4768 ('C', 'copies', None, _('show source of copied files')),
4762 ('C', 'copies', None, _('show source of copied files')),
4769 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4763 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4770 ('', 'rev', [], _('show difference from revision'), _('REV')),
4764 ('', 'rev', [], _('show difference from revision'), _('REV')),
4771 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4765 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4772 ] + walkopts + subrepoopts + formatteropts,
4766 ] + walkopts + subrepoopts + formatteropts,
4773 _('[OPTION]... [FILE]...'),
4767 _('[OPTION]... [FILE]...'),
4774 inferrepo=True, cmdtype=readonly)
4768 inferrepo=True, cmdtype=readonly)
4775 def status(ui, repo, *pats, **opts):
4769 def status(ui, repo, *pats, **opts):
4776 """show changed files in the working directory
4770 """show changed files in the working directory
4777
4771
4778 Show status of files in the repository. If names are given, only
4772 Show status of files in the repository. If names are given, only
4779 files that match are shown. Files that are clean or ignored or
4773 files that match are shown. Files that are clean or ignored or
4780 the source of a copy/move operation, are not listed unless
4774 the source of a copy/move operation, are not listed unless
4781 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4775 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4782 Unless options described with "show only ..." are given, the
4776 Unless options described with "show only ..." are given, the
4783 options -mardu are used.
4777 options -mardu are used.
4784
4778
4785 Option -q/--quiet hides untracked (unknown and ignored) files
4779 Option -q/--quiet hides untracked (unknown and ignored) files
4786 unless explicitly requested with -u/--unknown or -i/--ignored.
4780 unless explicitly requested with -u/--unknown or -i/--ignored.
4787
4781
4788 .. note::
4782 .. note::
4789
4783
4790 :hg:`status` may appear to disagree with diff if permissions have
4784 :hg:`status` may appear to disagree with diff if permissions have
4791 changed or a merge has occurred. The standard diff format does
4785 changed or a merge has occurred. The standard diff format does
4792 not report permission changes and diff only reports changes
4786 not report permission changes and diff only reports changes
4793 relative to one merge parent.
4787 relative to one merge parent.
4794
4788
4795 If one revision is given, it is used as the base revision.
4789 If one revision is given, it is used as the base revision.
4796 If two revisions are given, the differences between them are
4790 If two revisions are given, the differences between them are
4797 shown. The --change option can also be used as a shortcut to list
4791 shown. The --change option can also be used as a shortcut to list
4798 the changed files of a revision from its first parent.
4792 the changed files of a revision from its first parent.
4799
4793
4800 The codes used to show the status of files are::
4794 The codes used to show the status of files are::
4801
4795
4802 M = modified
4796 M = modified
4803 A = added
4797 A = added
4804 R = removed
4798 R = removed
4805 C = clean
4799 C = clean
4806 ! = missing (deleted by non-hg command, but still tracked)
4800 ! = missing (deleted by non-hg command, but still tracked)
4807 ? = not tracked
4801 ? = not tracked
4808 I = ignored
4802 I = ignored
4809 = origin of the previous file (with --copies)
4803 = origin of the previous file (with --copies)
4810
4804
4811 .. container:: verbose
4805 .. container:: verbose
4812
4806
4813 The -t/--terse option abbreviates the output by showing only the directory
4807 The -t/--terse option abbreviates the output by showing only the directory
4814 name if all the files in it share the same status. The option takes an
4808 name if all the files in it share the same status. The option takes an
4815 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
4809 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
4816 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
4810 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
4817 for 'ignored' and 'c' for clean.
4811 for 'ignored' and 'c' for clean.
4818
4812
4819 It abbreviates only those statuses which are passed. Note that clean and
4813 It abbreviates only those statuses which are passed. Note that clean and
4820 ignored files are not displayed with '--terse ic' unless the -c/--clean
4814 ignored files are not displayed with '--terse ic' unless the -c/--clean
4821 and -i/--ignored options are also used.
4815 and -i/--ignored options are also used.
4822
4816
4823 The -v/--verbose option shows information when the repository is in an
4817 The -v/--verbose option shows information when the repository is in an
4824 unfinished merge, shelve, rebase state etc. You can have this behavior
4818 unfinished merge, shelve, rebase state etc. You can have this behavior
4825 turned on by default by enabling the ``commands.status.verbose`` option.
4819 turned on by default by enabling the ``commands.status.verbose`` option.
4826
4820
4827 You can skip displaying some of these states by setting
4821 You can skip displaying some of these states by setting
4828 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
4822 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
4829 'histedit', 'merge', 'rebase', or 'unshelve'.
4823 'histedit', 'merge', 'rebase', or 'unshelve'.
4830
4824
4831 Examples:
4825 Examples:
4832
4826
4833 - show changes in the working directory relative to a
4827 - show changes in the working directory relative to a
4834 changeset::
4828 changeset::
4835
4829
4836 hg status --rev 9353
4830 hg status --rev 9353
4837
4831
4838 - show changes in the working directory relative to the
4832 - show changes in the working directory relative to the
4839 current directory (see :hg:`help patterns` for more information)::
4833 current directory (see :hg:`help patterns` for more information)::
4840
4834
4841 hg status re:
4835 hg status re:
4842
4836
4843 - show all changes including copies in an existing changeset::
4837 - show all changes including copies in an existing changeset::
4844
4838
4845 hg status --copies --change 9353
4839 hg status --copies --change 9353
4846
4840
4847 - get a NUL separated list of added files, suitable for xargs::
4841 - get a NUL separated list of added files, suitable for xargs::
4848
4842
4849 hg status -an0
4843 hg status -an0
4850
4844
4851 - show more information about the repository status, abbreviating
4845 - show more information about the repository status, abbreviating
4852 added, removed, modified, deleted, and untracked paths::
4846 added, removed, modified, deleted, and untracked paths::
4853
4847
4854 hg status -v -t mardu
4848 hg status -v -t mardu
4855
4849
4856 Returns 0 on success.
4850 Returns 0 on success.
4857
4851
4858 """
4852 """
4859
4853
4860 opts = pycompat.byteskwargs(opts)
4854 opts = pycompat.byteskwargs(opts)
4861 revs = opts.get('rev')
4855 revs = opts.get('rev')
4862 change = opts.get('change')
4856 change = opts.get('change')
4863 terse = opts.get('terse')
4857 terse = opts.get('terse')
4864
4858
4865 if revs and change:
4859 if revs and change:
4866 msg = _('cannot specify --rev and --change at the same time')
4860 msg = _('cannot specify --rev and --change at the same time')
4867 raise error.Abort(msg)
4861 raise error.Abort(msg)
4868 elif revs and terse:
4862 elif revs and terse:
4869 msg = _('cannot use --terse with --rev')
4863 msg = _('cannot use --terse with --rev')
4870 raise error.Abort(msg)
4864 raise error.Abort(msg)
4871 elif change:
4865 elif change:
4872 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
4866 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
4873 node2 = scmutil.revsingle(repo, change, None).node()
4867 node2 = scmutil.revsingle(repo, change, None).node()
4874 node1 = repo[node2].p1().node()
4868 node1 = repo[node2].p1().node()
4875 else:
4869 else:
4876 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
4870 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
4877 node1, node2 = scmutil.revpair(repo, revs)
4871 node1, node2 = scmutil.revpair(repo, revs)
4878
4872
4879 if pats or ui.configbool('commands', 'status.relative'):
4873 if pats or ui.configbool('commands', 'status.relative'):
4880 cwd = repo.getcwd()
4874 cwd = repo.getcwd()
4881 else:
4875 else:
4882 cwd = ''
4876 cwd = ''
4883
4877
4884 if opts.get('print0'):
4878 if opts.get('print0'):
4885 end = '\0'
4879 end = '\0'
4886 else:
4880 else:
4887 end = '\n'
4881 end = '\n'
4888 copy = {}
4882 copy = {}
4889 states = 'modified added removed deleted unknown ignored clean'.split()
4883 states = 'modified added removed deleted unknown ignored clean'.split()
4890 show = [k for k in states if opts.get(k)]
4884 show = [k for k in states if opts.get(k)]
4891 if opts.get('all'):
4885 if opts.get('all'):
4892 show += ui.quiet and (states[:4] + ['clean']) or states
4886 show += ui.quiet and (states[:4] + ['clean']) or states
4893
4887
4894 if not show:
4888 if not show:
4895 if ui.quiet:
4889 if ui.quiet:
4896 show = states[:4]
4890 show = states[:4]
4897 else:
4891 else:
4898 show = states[:5]
4892 show = states[:5]
4899
4893
4900 m = scmutil.match(repo[node2], pats, opts)
4894 m = scmutil.match(repo[node2], pats, opts)
4901 if terse:
4895 if terse:
4902 # we need to compute clean and unknown to terse
4896 # we need to compute clean and unknown to terse
4903 stat = repo.status(node1, node2, m,
4897 stat = repo.status(node1, node2, m,
4904 'ignored' in show or 'i' in terse,
4898 'ignored' in show or 'i' in terse,
4905 True, True, opts.get('subrepos'))
4899 True, True, opts.get('subrepos'))
4906
4900
4907 stat = cmdutil.tersedir(stat, terse)
4901 stat = cmdutil.tersedir(stat, terse)
4908 else:
4902 else:
4909 stat = repo.status(node1, node2, m,
4903 stat = repo.status(node1, node2, m,
4910 'ignored' in show, 'clean' in show,
4904 'ignored' in show, 'clean' in show,
4911 'unknown' in show, opts.get('subrepos'))
4905 'unknown' in show, opts.get('subrepos'))
4912
4906
4913 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
4907 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
4914
4908
4915 if (opts.get('all') or opts.get('copies')
4909 if (opts.get('all') or opts.get('copies')
4916 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
4910 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
4917 copy = copies.pathcopies(repo[node1], repo[node2], m)
4911 copy = copies.pathcopies(repo[node1], repo[node2], m)
4918
4912
4919 ui.pager('status')
4913 ui.pager('status')
4920 fm = ui.formatter('status', opts)
4914 fm = ui.formatter('status', opts)
4921 fmt = '%s' + end
4915 fmt = '%s' + end
4922 showchar = not opts.get('no_status')
4916 showchar = not opts.get('no_status')
4923
4917
4924 for state, char, files in changestates:
4918 for state, char, files in changestates:
4925 if state in show:
4919 if state in show:
4926 label = 'status.' + state
4920 label = 'status.' + state
4927 for f in files:
4921 for f in files:
4928 fm.startitem()
4922 fm.startitem()
4929 fm.condwrite(showchar, 'status', '%s ', char, label=label)
4923 fm.condwrite(showchar, 'status', '%s ', char, label=label)
4930 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
4924 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
4931 if f in copy:
4925 if f in copy:
4932 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
4926 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
4933 label='status.copied')
4927 label='status.copied')
4934
4928
4935 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
4929 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
4936 and not ui.plain()):
4930 and not ui.plain()):
4937 cmdutil.morestatus(repo, fm)
4931 cmdutil.morestatus(repo, fm)
4938 fm.end()
4932 fm.end()
4939
4933
4940 @command('^summary|sum',
4934 @command('^summary|sum',
4941 [('', 'remote', None, _('check for push and pull'))],
4935 [('', 'remote', None, _('check for push and pull'))],
4942 '[--remote]', cmdtype=readonly)
4936 '[--remote]', cmdtype=readonly)
4943 def summary(ui, repo, **opts):
4937 def summary(ui, repo, **opts):
4944 """summarize working directory state
4938 """summarize working directory state
4945
4939
4946 This generates a brief summary of the working directory state,
4940 This generates a brief summary of the working directory state,
4947 including parents, branch, commit status, phase and available updates.
4941 including parents, branch, commit status, phase and available updates.
4948
4942
4949 With the --remote option, this will check the default paths for
4943 With the --remote option, this will check the default paths for
4950 incoming and outgoing changes. This can be time-consuming.
4944 incoming and outgoing changes. This can be time-consuming.
4951
4945
4952 Returns 0 on success.
4946 Returns 0 on success.
4953 """
4947 """
4954
4948
4955 opts = pycompat.byteskwargs(opts)
4949 opts = pycompat.byteskwargs(opts)
4956 ui.pager('summary')
4950 ui.pager('summary')
4957 ctx = repo[None]
4951 ctx = repo[None]
4958 parents = ctx.parents()
4952 parents = ctx.parents()
4959 pnode = parents[0].node()
4953 pnode = parents[0].node()
4960 marks = []
4954 marks = []
4961
4955
4962 ms = None
4956 ms = None
4963 try:
4957 try:
4964 ms = mergemod.mergestate.read(repo)
4958 ms = mergemod.mergestate.read(repo)
4965 except error.UnsupportedMergeRecords as e:
4959 except error.UnsupportedMergeRecords as e:
4966 s = ' '.join(e.recordtypes)
4960 s = ' '.join(e.recordtypes)
4967 ui.warn(
4961 ui.warn(
4968 _('warning: merge state has unsupported record types: %s\n') % s)
4962 _('warning: merge state has unsupported record types: %s\n') % s)
4969 unresolved = []
4963 unresolved = []
4970 else:
4964 else:
4971 unresolved = list(ms.unresolved())
4965 unresolved = list(ms.unresolved())
4972
4966
4973 for p in parents:
4967 for p in parents:
4974 # label with log.changeset (instead of log.parent) since this
4968 # label with log.changeset (instead of log.parent) since this
4975 # shows a working directory parent *changeset*:
4969 # shows a working directory parent *changeset*:
4976 # i18n: column positioning for "hg summary"
4970 # i18n: column positioning for "hg summary"
4977 ui.write(_('parent: %d:%s ') % (p.rev(), p),
4971 ui.write(_('parent: %d:%s ') % (p.rev(), p),
4978 label=cmdutil._changesetlabels(p))
4972 label=cmdutil._changesetlabels(p))
4979 ui.write(' '.join(p.tags()), label='log.tag')
4973 ui.write(' '.join(p.tags()), label='log.tag')
4980 if p.bookmarks():
4974 if p.bookmarks():
4981 marks.extend(p.bookmarks())
4975 marks.extend(p.bookmarks())
4982 if p.rev() == -1:
4976 if p.rev() == -1:
4983 if not len(repo):
4977 if not len(repo):
4984 ui.write(_(' (empty repository)'))
4978 ui.write(_(' (empty repository)'))
4985 else:
4979 else:
4986 ui.write(_(' (no revision checked out)'))
4980 ui.write(_(' (no revision checked out)'))
4987 if p.obsolete():
4981 if p.obsolete():
4988 ui.write(_(' (obsolete)'))
4982 ui.write(_(' (obsolete)'))
4989 if p.isunstable():
4983 if p.isunstable():
4990 instabilities = (ui.label(instability, 'trouble.%s' % instability)
4984 instabilities = (ui.label(instability, 'trouble.%s' % instability)
4991 for instability in p.instabilities())
4985 for instability in p.instabilities())
4992 ui.write(' ('
4986 ui.write(' ('
4993 + ', '.join(instabilities)
4987 + ', '.join(instabilities)
4994 + ')')
4988 + ')')
4995 ui.write('\n')
4989 ui.write('\n')
4996 if p.description():
4990 if p.description():
4997 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
4991 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
4998 label='log.summary')
4992 label='log.summary')
4999
4993
5000 branch = ctx.branch()
4994 branch = ctx.branch()
5001 bheads = repo.branchheads(branch)
4995 bheads = repo.branchheads(branch)
5002 # i18n: column positioning for "hg summary"
4996 # i18n: column positioning for "hg summary"
5003 m = _('branch: %s\n') % branch
4997 m = _('branch: %s\n') % branch
5004 if branch != 'default':
4998 if branch != 'default':
5005 ui.write(m, label='log.branch')
4999 ui.write(m, label='log.branch')
5006 else:
5000 else:
5007 ui.status(m, label='log.branch')
5001 ui.status(m, label='log.branch')
5008
5002
5009 if marks:
5003 if marks:
5010 active = repo._activebookmark
5004 active = repo._activebookmark
5011 # i18n: column positioning for "hg summary"
5005 # i18n: column positioning for "hg summary"
5012 ui.write(_('bookmarks:'), label='log.bookmark')
5006 ui.write(_('bookmarks:'), label='log.bookmark')
5013 if active is not None:
5007 if active is not None:
5014 if active in marks:
5008 if active in marks:
5015 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5009 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5016 marks.remove(active)
5010 marks.remove(active)
5017 else:
5011 else:
5018 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5012 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5019 for m in marks:
5013 for m in marks:
5020 ui.write(' ' + m, label='log.bookmark')
5014 ui.write(' ' + m, label='log.bookmark')
5021 ui.write('\n', label='log.bookmark')
5015 ui.write('\n', label='log.bookmark')
5022
5016
5023 status = repo.status(unknown=True)
5017 status = repo.status(unknown=True)
5024
5018
5025 c = repo.dirstate.copies()
5019 c = repo.dirstate.copies()
5026 copied, renamed = [], []
5020 copied, renamed = [], []
5027 for d, s in c.iteritems():
5021 for d, s in c.iteritems():
5028 if s in status.removed:
5022 if s in status.removed:
5029 status.removed.remove(s)
5023 status.removed.remove(s)
5030 renamed.append(d)
5024 renamed.append(d)
5031 else:
5025 else:
5032 copied.append(d)
5026 copied.append(d)
5033 if d in status.added:
5027 if d in status.added:
5034 status.added.remove(d)
5028 status.added.remove(d)
5035
5029
5036 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5030 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5037
5031
5038 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5032 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5039 (ui.label(_('%d added'), 'status.added'), status.added),
5033 (ui.label(_('%d added'), 'status.added'), status.added),
5040 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5034 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5041 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5035 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5042 (ui.label(_('%d copied'), 'status.copied'), copied),
5036 (ui.label(_('%d copied'), 'status.copied'), copied),
5043 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5037 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5044 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5038 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5045 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5039 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5046 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5040 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5047 t = []
5041 t = []
5048 for l, s in labels:
5042 for l, s in labels:
5049 if s:
5043 if s:
5050 t.append(l % len(s))
5044 t.append(l % len(s))
5051
5045
5052 t = ', '.join(t)
5046 t = ', '.join(t)
5053 cleanworkdir = False
5047 cleanworkdir = False
5054
5048
5055 if repo.vfs.exists('graftstate'):
5049 if repo.vfs.exists('graftstate'):
5056 t += _(' (graft in progress)')
5050 t += _(' (graft in progress)')
5057 if repo.vfs.exists('updatestate'):
5051 if repo.vfs.exists('updatestate'):
5058 t += _(' (interrupted update)')
5052 t += _(' (interrupted update)')
5059 elif len(parents) > 1:
5053 elif len(parents) > 1:
5060 t += _(' (merge)')
5054 t += _(' (merge)')
5061 elif branch != parents[0].branch():
5055 elif branch != parents[0].branch():
5062 t += _(' (new branch)')
5056 t += _(' (new branch)')
5063 elif (parents[0].closesbranch() and
5057 elif (parents[0].closesbranch() and
5064 pnode in repo.branchheads(branch, closed=True)):
5058 pnode in repo.branchheads(branch, closed=True)):
5065 t += _(' (head closed)')
5059 t += _(' (head closed)')
5066 elif not (status.modified or status.added or status.removed or renamed or
5060 elif not (status.modified or status.added or status.removed or renamed or
5067 copied or subs):
5061 copied or subs):
5068 t += _(' (clean)')
5062 t += _(' (clean)')
5069 cleanworkdir = True
5063 cleanworkdir = True
5070 elif pnode not in bheads:
5064 elif pnode not in bheads:
5071 t += _(' (new branch head)')
5065 t += _(' (new branch head)')
5072
5066
5073 if parents:
5067 if parents:
5074 pendingphase = max(p.phase() for p in parents)
5068 pendingphase = max(p.phase() for p in parents)
5075 else:
5069 else:
5076 pendingphase = phases.public
5070 pendingphase = phases.public
5077
5071
5078 if pendingphase > phases.newcommitphase(ui):
5072 if pendingphase > phases.newcommitphase(ui):
5079 t += ' (%s)' % phases.phasenames[pendingphase]
5073 t += ' (%s)' % phases.phasenames[pendingphase]
5080
5074
5081 if cleanworkdir:
5075 if cleanworkdir:
5082 # i18n: column positioning for "hg summary"
5076 # i18n: column positioning for "hg summary"
5083 ui.status(_('commit: %s\n') % t.strip())
5077 ui.status(_('commit: %s\n') % t.strip())
5084 else:
5078 else:
5085 # i18n: column positioning for "hg summary"
5079 # i18n: column positioning for "hg summary"
5086 ui.write(_('commit: %s\n') % t.strip())
5080 ui.write(_('commit: %s\n') % t.strip())
5087
5081
5088 # all ancestors of branch heads - all ancestors of parent = new csets
5082 # all ancestors of branch heads - all ancestors of parent = new csets
5089 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5083 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5090 bheads))
5084 bheads))
5091
5085
5092 if new == 0:
5086 if new == 0:
5093 # i18n: column positioning for "hg summary"
5087 # i18n: column positioning for "hg summary"
5094 ui.status(_('update: (current)\n'))
5088 ui.status(_('update: (current)\n'))
5095 elif pnode not in bheads:
5089 elif pnode not in bheads:
5096 # i18n: column positioning for "hg summary"
5090 # i18n: column positioning for "hg summary"
5097 ui.write(_('update: %d new changesets (update)\n') % new)
5091 ui.write(_('update: %d new changesets (update)\n') % new)
5098 else:
5092 else:
5099 # i18n: column positioning for "hg summary"
5093 # i18n: column positioning for "hg summary"
5100 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5094 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5101 (new, len(bheads)))
5095 (new, len(bheads)))
5102
5096
5103 t = []
5097 t = []
5104 draft = len(repo.revs('draft()'))
5098 draft = len(repo.revs('draft()'))
5105 if draft:
5099 if draft:
5106 t.append(_('%d draft') % draft)
5100 t.append(_('%d draft') % draft)
5107 secret = len(repo.revs('secret()'))
5101 secret = len(repo.revs('secret()'))
5108 if secret:
5102 if secret:
5109 t.append(_('%d secret') % secret)
5103 t.append(_('%d secret') % secret)
5110
5104
5111 if draft or secret:
5105 if draft or secret:
5112 ui.status(_('phases: %s\n') % ', '.join(t))
5106 ui.status(_('phases: %s\n') % ', '.join(t))
5113
5107
5114 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5108 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5115 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5109 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5116 numtrouble = len(repo.revs(trouble + "()"))
5110 numtrouble = len(repo.revs(trouble + "()"))
5117 # We write all the possibilities to ease translation
5111 # We write all the possibilities to ease translation
5118 troublemsg = {
5112 troublemsg = {
5119 "orphan": _("orphan: %d changesets"),
5113 "orphan": _("orphan: %d changesets"),
5120 "contentdivergent": _("content-divergent: %d changesets"),
5114 "contentdivergent": _("content-divergent: %d changesets"),
5121 "phasedivergent": _("phase-divergent: %d changesets"),
5115 "phasedivergent": _("phase-divergent: %d changesets"),
5122 }
5116 }
5123 if numtrouble > 0:
5117 if numtrouble > 0:
5124 ui.status(troublemsg[trouble] % numtrouble + "\n")
5118 ui.status(troublemsg[trouble] % numtrouble + "\n")
5125
5119
5126 cmdutil.summaryhooks(ui, repo)
5120 cmdutil.summaryhooks(ui, repo)
5127
5121
5128 if opts.get('remote'):
5122 if opts.get('remote'):
5129 needsincoming, needsoutgoing = True, True
5123 needsincoming, needsoutgoing = True, True
5130 else:
5124 else:
5131 needsincoming, needsoutgoing = False, False
5125 needsincoming, needsoutgoing = False, False
5132 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5126 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5133 if i:
5127 if i:
5134 needsincoming = True
5128 needsincoming = True
5135 if o:
5129 if o:
5136 needsoutgoing = True
5130 needsoutgoing = True
5137 if not needsincoming and not needsoutgoing:
5131 if not needsincoming and not needsoutgoing:
5138 return
5132 return
5139
5133
5140 def getincoming():
5134 def getincoming():
5141 source, branches = hg.parseurl(ui.expandpath('default'))
5135 source, branches = hg.parseurl(ui.expandpath('default'))
5142 sbranch = branches[0]
5136 sbranch = branches[0]
5143 try:
5137 try:
5144 other = hg.peer(repo, {}, source)
5138 other = hg.peer(repo, {}, source)
5145 except error.RepoError:
5139 except error.RepoError:
5146 if opts.get('remote'):
5140 if opts.get('remote'):
5147 raise
5141 raise
5148 return source, sbranch, None, None, None
5142 return source, sbranch, None, None, None
5149 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5143 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5150 if revs:
5144 if revs:
5151 revs = [other.lookup(rev) for rev in revs]
5145 revs = [other.lookup(rev) for rev in revs]
5152 ui.debug('comparing with %s\n' % util.hidepassword(source))
5146 ui.debug('comparing with %s\n' % util.hidepassword(source))
5153 repo.ui.pushbuffer()
5147 repo.ui.pushbuffer()
5154 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5148 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5155 repo.ui.popbuffer()
5149 repo.ui.popbuffer()
5156 return source, sbranch, other, commoninc, commoninc[1]
5150 return source, sbranch, other, commoninc, commoninc[1]
5157
5151
5158 if needsincoming:
5152 if needsincoming:
5159 source, sbranch, sother, commoninc, incoming = getincoming()
5153 source, sbranch, sother, commoninc, incoming = getincoming()
5160 else:
5154 else:
5161 source = sbranch = sother = commoninc = incoming = None
5155 source = sbranch = sother = commoninc = incoming = None
5162
5156
5163 def getoutgoing():
5157 def getoutgoing():
5164 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5158 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5165 dbranch = branches[0]
5159 dbranch = branches[0]
5166 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5160 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5167 if source != dest:
5161 if source != dest:
5168 try:
5162 try:
5169 dother = hg.peer(repo, {}, dest)
5163 dother = hg.peer(repo, {}, dest)
5170 except error.RepoError:
5164 except error.RepoError:
5171 if opts.get('remote'):
5165 if opts.get('remote'):
5172 raise
5166 raise
5173 return dest, dbranch, None, None
5167 return dest, dbranch, None, None
5174 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5168 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5175 elif sother is None:
5169 elif sother is None:
5176 # there is no explicit destination peer, but source one is invalid
5170 # there is no explicit destination peer, but source one is invalid
5177 return dest, dbranch, None, None
5171 return dest, dbranch, None, None
5178 else:
5172 else:
5179 dother = sother
5173 dother = sother
5180 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5174 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5181 common = None
5175 common = None
5182 else:
5176 else:
5183 common = commoninc
5177 common = commoninc
5184 if revs:
5178 if revs:
5185 revs = [repo.lookup(rev) for rev in revs]
5179 revs = [repo.lookup(rev) for rev in revs]
5186 repo.ui.pushbuffer()
5180 repo.ui.pushbuffer()
5187 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5181 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5188 commoninc=common)
5182 commoninc=common)
5189 repo.ui.popbuffer()
5183 repo.ui.popbuffer()
5190 return dest, dbranch, dother, outgoing
5184 return dest, dbranch, dother, outgoing
5191
5185
5192 if needsoutgoing:
5186 if needsoutgoing:
5193 dest, dbranch, dother, outgoing = getoutgoing()
5187 dest, dbranch, dother, outgoing = getoutgoing()
5194 else:
5188 else:
5195 dest = dbranch = dother = outgoing = None
5189 dest = dbranch = dother = outgoing = None
5196
5190
5197 if opts.get('remote'):
5191 if opts.get('remote'):
5198 t = []
5192 t = []
5199 if incoming:
5193 if incoming:
5200 t.append(_('1 or more incoming'))
5194 t.append(_('1 or more incoming'))
5201 o = outgoing.missing
5195 o = outgoing.missing
5202 if o:
5196 if o:
5203 t.append(_('%d outgoing') % len(o))
5197 t.append(_('%d outgoing') % len(o))
5204 other = dother or sother
5198 other = dother or sother
5205 if 'bookmarks' in other.listkeys('namespaces'):
5199 if 'bookmarks' in other.listkeys('namespaces'):
5206 counts = bookmarks.summary(repo, other)
5200 counts = bookmarks.summary(repo, other)
5207 if counts[0] > 0:
5201 if counts[0] > 0:
5208 t.append(_('%d incoming bookmarks') % counts[0])
5202 t.append(_('%d incoming bookmarks') % counts[0])
5209 if counts[1] > 0:
5203 if counts[1] > 0:
5210 t.append(_('%d outgoing bookmarks') % counts[1])
5204 t.append(_('%d outgoing bookmarks') % counts[1])
5211
5205
5212 if t:
5206 if t:
5213 # i18n: column positioning for "hg summary"
5207 # i18n: column positioning for "hg summary"
5214 ui.write(_('remote: %s\n') % (', '.join(t)))
5208 ui.write(_('remote: %s\n') % (', '.join(t)))
5215 else:
5209 else:
5216 # i18n: column positioning for "hg summary"
5210 # i18n: column positioning for "hg summary"
5217 ui.status(_('remote: (synced)\n'))
5211 ui.status(_('remote: (synced)\n'))
5218
5212
5219 cmdutil.summaryremotehooks(ui, repo, opts,
5213 cmdutil.summaryremotehooks(ui, repo, opts,
5220 ((source, sbranch, sother, commoninc),
5214 ((source, sbranch, sother, commoninc),
5221 (dest, dbranch, dother, outgoing)))
5215 (dest, dbranch, dother, outgoing)))
5222
5216
5223 @command('tag',
5217 @command('tag',
5224 [('f', 'force', None, _('force tag')),
5218 [('f', 'force', None, _('force tag')),
5225 ('l', 'local', None, _('make the tag local')),
5219 ('l', 'local', None, _('make the tag local')),
5226 ('r', 'rev', '', _('revision to tag'), _('REV')),
5220 ('r', 'rev', '', _('revision to tag'), _('REV')),
5227 ('', 'remove', None, _('remove a tag')),
5221 ('', 'remove', None, _('remove a tag')),
5228 # -l/--local is already there, commitopts cannot be used
5222 # -l/--local is already there, commitopts cannot be used
5229 ('e', 'edit', None, _('invoke editor on commit messages')),
5223 ('e', 'edit', None, _('invoke editor on commit messages')),
5230 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5224 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5231 ] + commitopts2,
5225 ] + commitopts2,
5232 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5226 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5233 def tag(ui, repo, name1, *names, **opts):
5227 def tag(ui, repo, name1, *names, **opts):
5234 """add one or more tags for the current or given revision
5228 """add one or more tags for the current or given revision
5235
5229
5236 Name a particular revision using <name>.
5230 Name a particular revision using <name>.
5237
5231
5238 Tags are used to name particular revisions of the repository and are
5232 Tags are used to name particular revisions of the repository and are
5239 very useful to compare different revisions, to go back to significant
5233 very useful to compare different revisions, to go back to significant
5240 earlier versions or to mark branch points as releases, etc. Changing
5234 earlier versions or to mark branch points as releases, etc. Changing
5241 an existing tag is normally disallowed; use -f/--force to override.
5235 an existing tag is normally disallowed; use -f/--force to override.
5242
5236
5243 If no revision is given, the parent of the working directory is
5237 If no revision is given, the parent of the working directory is
5244 used.
5238 used.
5245
5239
5246 To facilitate version control, distribution, and merging of tags,
5240 To facilitate version control, distribution, and merging of tags,
5247 they are stored as a file named ".hgtags" which is managed similarly
5241 they are stored as a file named ".hgtags" which is managed similarly
5248 to other project files and can be hand-edited if necessary. This
5242 to other project files and can be hand-edited if necessary. This
5249 also means that tagging creates a new commit. The file
5243 also means that tagging creates a new commit. The file
5250 ".hg/localtags" is used for local tags (not shared among
5244 ".hg/localtags" is used for local tags (not shared among
5251 repositories).
5245 repositories).
5252
5246
5253 Tag commits are usually made at the head of a branch. If the parent
5247 Tag commits are usually made at the head of a branch. If the parent
5254 of the working directory is not a branch head, :hg:`tag` aborts; use
5248 of the working directory is not a branch head, :hg:`tag` aborts; use
5255 -f/--force to force the tag commit to be based on a non-head
5249 -f/--force to force the tag commit to be based on a non-head
5256 changeset.
5250 changeset.
5257
5251
5258 See :hg:`help dates` for a list of formats valid for -d/--date.
5252 See :hg:`help dates` for a list of formats valid for -d/--date.
5259
5253
5260 Since tag names have priority over branch names during revision
5254 Since tag names have priority over branch names during revision
5261 lookup, using an existing branch name as a tag name is discouraged.
5255 lookup, using an existing branch name as a tag name is discouraged.
5262
5256
5263 Returns 0 on success.
5257 Returns 0 on success.
5264 """
5258 """
5265 opts = pycompat.byteskwargs(opts)
5259 opts = pycompat.byteskwargs(opts)
5266 wlock = lock = None
5260 wlock = lock = None
5267 try:
5261 try:
5268 wlock = repo.wlock()
5262 wlock = repo.wlock()
5269 lock = repo.lock()
5263 lock = repo.lock()
5270 rev_ = "."
5264 rev_ = "."
5271 names = [t.strip() for t in (name1,) + names]
5265 names = [t.strip() for t in (name1,) + names]
5272 if len(names) != len(set(names)):
5266 if len(names) != len(set(names)):
5273 raise error.Abort(_('tag names must be unique'))
5267 raise error.Abort(_('tag names must be unique'))
5274 for n in names:
5268 for n in names:
5275 scmutil.checknewlabel(repo, n, 'tag')
5269 scmutil.checknewlabel(repo, n, 'tag')
5276 if not n:
5270 if not n:
5277 raise error.Abort(_('tag names cannot consist entirely of '
5271 raise error.Abort(_('tag names cannot consist entirely of '
5278 'whitespace'))
5272 'whitespace'))
5279 if opts.get('rev') and opts.get('remove'):
5273 if opts.get('rev') and opts.get('remove'):
5280 raise error.Abort(_("--rev and --remove are incompatible"))
5274 raise error.Abort(_("--rev and --remove are incompatible"))
5281 if opts.get('rev'):
5275 if opts.get('rev'):
5282 rev_ = opts['rev']
5276 rev_ = opts['rev']
5283 message = opts.get('message')
5277 message = opts.get('message')
5284 if opts.get('remove'):
5278 if opts.get('remove'):
5285 if opts.get('local'):
5279 if opts.get('local'):
5286 expectedtype = 'local'
5280 expectedtype = 'local'
5287 else:
5281 else:
5288 expectedtype = 'global'
5282 expectedtype = 'global'
5289
5283
5290 for n in names:
5284 for n in names:
5291 if not repo.tagtype(n):
5285 if not repo.tagtype(n):
5292 raise error.Abort(_("tag '%s' does not exist") % n)
5286 raise error.Abort(_("tag '%s' does not exist") % n)
5293 if repo.tagtype(n) != expectedtype:
5287 if repo.tagtype(n) != expectedtype:
5294 if expectedtype == 'global':
5288 if expectedtype == 'global':
5295 raise error.Abort(_("tag '%s' is not a global tag") % n)
5289 raise error.Abort(_("tag '%s' is not a global tag") % n)
5296 else:
5290 else:
5297 raise error.Abort(_("tag '%s' is not a local tag") % n)
5291 raise error.Abort(_("tag '%s' is not a local tag") % n)
5298 rev_ = 'null'
5292 rev_ = 'null'
5299 if not message:
5293 if not message:
5300 # we don't translate commit messages
5294 # we don't translate commit messages
5301 message = 'Removed tag %s' % ', '.join(names)
5295 message = 'Removed tag %s' % ', '.join(names)
5302 elif not opts.get('force'):
5296 elif not opts.get('force'):
5303 for n in names:
5297 for n in names:
5304 if n in repo.tags():
5298 if n in repo.tags():
5305 raise error.Abort(_("tag '%s' already exists "
5299 raise error.Abort(_("tag '%s' already exists "
5306 "(use -f to force)") % n)
5300 "(use -f to force)") % n)
5307 if not opts.get('local'):
5301 if not opts.get('local'):
5308 p1, p2 = repo.dirstate.parents()
5302 p1, p2 = repo.dirstate.parents()
5309 if p2 != nullid:
5303 if p2 != nullid:
5310 raise error.Abort(_('uncommitted merge'))
5304 raise error.Abort(_('uncommitted merge'))
5311 bheads = repo.branchheads()
5305 bheads = repo.branchheads()
5312 if not opts.get('force') and bheads and p1 not in bheads:
5306 if not opts.get('force') and bheads and p1 not in bheads:
5313 raise error.Abort(_('working directory is not at a branch head '
5307 raise error.Abort(_('working directory is not at a branch head '
5314 '(use -f to force)'))
5308 '(use -f to force)'))
5315 r = scmutil.revsingle(repo, rev_).node()
5309 r = scmutil.revsingle(repo, rev_).node()
5316
5310
5317 if not message:
5311 if not message:
5318 # we don't translate commit messages
5312 # we don't translate commit messages
5319 message = ('Added tag %s for changeset %s' %
5313 message = ('Added tag %s for changeset %s' %
5320 (', '.join(names), short(r)))
5314 (', '.join(names), short(r)))
5321
5315
5322 date = opts.get('date')
5316 date = opts.get('date')
5323 if date:
5317 if date:
5324 date = util.parsedate(date)
5318 date = util.parsedate(date)
5325
5319
5326 if opts.get('remove'):
5320 if opts.get('remove'):
5327 editform = 'tag.remove'
5321 editform = 'tag.remove'
5328 else:
5322 else:
5329 editform = 'tag.add'
5323 editform = 'tag.add'
5330 editor = cmdutil.getcommiteditor(editform=editform,
5324 editor = cmdutil.getcommiteditor(editform=editform,
5331 **pycompat.strkwargs(opts))
5325 **pycompat.strkwargs(opts))
5332
5326
5333 # don't allow tagging the null rev
5327 # don't allow tagging the null rev
5334 if (not opts.get('remove') and
5328 if (not opts.get('remove') and
5335 scmutil.revsingle(repo, rev_).rev() == nullrev):
5329 scmutil.revsingle(repo, rev_).rev() == nullrev):
5336 raise error.Abort(_("cannot tag null revision"))
5330 raise error.Abort(_("cannot tag null revision"))
5337
5331
5338 tagsmod.tag(repo, names, r, message, opts.get('local'),
5332 tagsmod.tag(repo, names, r, message, opts.get('local'),
5339 opts.get('user'), date, editor=editor)
5333 opts.get('user'), date, editor=editor)
5340 finally:
5334 finally:
5341 release(lock, wlock)
5335 release(lock, wlock)
5342
5336
5343 @command('tags', formatteropts, '', cmdtype=readonly)
5337 @command('tags', formatteropts, '', cmdtype=readonly)
5344 def tags(ui, repo, **opts):
5338 def tags(ui, repo, **opts):
5345 """list repository tags
5339 """list repository tags
5346
5340
5347 This lists both regular and local tags. When the -v/--verbose
5341 This lists both regular and local tags. When the -v/--verbose
5348 switch is used, a third column "local" is printed for local tags.
5342 switch is used, a third column "local" is printed for local tags.
5349 When the -q/--quiet switch is used, only the tag name is printed.
5343 When the -q/--quiet switch is used, only the tag name is printed.
5350
5344
5351 Returns 0 on success.
5345 Returns 0 on success.
5352 """
5346 """
5353
5347
5354 opts = pycompat.byteskwargs(opts)
5348 opts = pycompat.byteskwargs(opts)
5355 ui.pager('tags')
5349 ui.pager('tags')
5356 fm = ui.formatter('tags', opts)
5350 fm = ui.formatter('tags', opts)
5357 hexfunc = fm.hexfunc
5351 hexfunc = fm.hexfunc
5358 tagtype = ""
5352 tagtype = ""
5359
5353
5360 for t, n in reversed(repo.tagslist()):
5354 for t, n in reversed(repo.tagslist()):
5361 hn = hexfunc(n)
5355 hn = hexfunc(n)
5362 label = 'tags.normal'
5356 label = 'tags.normal'
5363 tagtype = ''
5357 tagtype = ''
5364 if repo.tagtype(t) == 'local':
5358 if repo.tagtype(t) == 'local':
5365 label = 'tags.local'
5359 label = 'tags.local'
5366 tagtype = 'local'
5360 tagtype = 'local'
5367
5361
5368 fm.startitem()
5362 fm.startitem()
5369 fm.write('tag', '%s', t, label=label)
5363 fm.write('tag', '%s', t, label=label)
5370 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5364 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5371 fm.condwrite(not ui.quiet, 'rev node', fmt,
5365 fm.condwrite(not ui.quiet, 'rev node', fmt,
5372 repo.changelog.rev(n), hn, label=label)
5366 repo.changelog.rev(n), hn, label=label)
5373 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5367 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5374 tagtype, label=label)
5368 tagtype, label=label)
5375 fm.plain('\n')
5369 fm.plain('\n')
5376 fm.end()
5370 fm.end()
5377
5371
5378 @command('tip',
5372 @command('tip',
5379 [('p', 'patch', None, _('show patch')),
5373 [('p', 'patch', None, _('show patch')),
5380 ('g', 'git', None, _('use git extended diff format')),
5374 ('g', 'git', None, _('use git extended diff format')),
5381 ] + templateopts,
5375 ] + templateopts,
5382 _('[-p] [-g]'))
5376 _('[-p] [-g]'))
5383 def tip(ui, repo, **opts):
5377 def tip(ui, repo, **opts):
5384 """show the tip revision (DEPRECATED)
5378 """show the tip revision (DEPRECATED)
5385
5379
5386 The tip revision (usually just called the tip) is the changeset
5380 The tip revision (usually just called the tip) is the changeset
5387 most recently added to the repository (and therefore the most
5381 most recently added to the repository (and therefore the most
5388 recently changed head).
5382 recently changed head).
5389
5383
5390 If you have just made a commit, that commit will be the tip. If
5384 If you have just made a commit, that commit will be the tip. If
5391 you have just pulled changes from another repository, the tip of
5385 you have just pulled changes from another repository, the tip of
5392 that repository becomes the current tip. The "tip" tag is special
5386 that repository becomes the current tip. The "tip" tag is special
5393 and cannot be renamed or assigned to a different changeset.
5387 and cannot be renamed or assigned to a different changeset.
5394
5388
5395 This command is deprecated, please use :hg:`heads` instead.
5389 This command is deprecated, please use :hg:`heads` instead.
5396
5390
5397 Returns 0 on success.
5391 Returns 0 on success.
5398 """
5392 """
5399 opts = pycompat.byteskwargs(opts)
5393 opts = pycompat.byteskwargs(opts)
5400 displayer = cmdutil.show_changeset(ui, repo, opts)
5394 displayer = cmdutil.show_changeset(ui, repo, opts)
5401 displayer.show(repo['tip'])
5395 displayer.show(repo['tip'])
5402 displayer.close()
5396 displayer.close()
5403
5397
5404 @command('unbundle',
5398 @command('unbundle',
5405 [('u', 'update', None,
5399 [('u', 'update', None,
5406 _('update to new branch head if changesets were unbundled'))],
5400 _('update to new branch head if changesets were unbundled'))],
5407 _('[-u] FILE...'))
5401 _('[-u] FILE...'))
5408 def unbundle(ui, repo, fname1, *fnames, **opts):
5402 def unbundle(ui, repo, fname1, *fnames, **opts):
5409 """apply one or more bundle files
5403 """apply one or more bundle files
5410
5404
5411 Apply one or more bundle files generated by :hg:`bundle`.
5405 Apply one or more bundle files generated by :hg:`bundle`.
5412
5406
5413 Returns 0 on success, 1 if an update has unresolved files.
5407 Returns 0 on success, 1 if an update has unresolved files.
5414 """
5408 """
5415 fnames = (fname1,) + fnames
5409 fnames = (fname1,) + fnames
5416
5410
5417 with repo.lock():
5411 with repo.lock():
5418 for fname in fnames:
5412 for fname in fnames:
5419 f = hg.openpath(ui, fname)
5413 f = hg.openpath(ui, fname)
5420 gen = exchange.readbundle(ui, f, fname)
5414 gen = exchange.readbundle(ui, f, fname)
5421 if isinstance(gen, streamclone.streamcloneapplier):
5415 if isinstance(gen, streamclone.streamcloneapplier):
5422 raise error.Abort(
5416 raise error.Abort(
5423 _('packed bundles cannot be applied with '
5417 _('packed bundles cannot be applied with '
5424 '"hg unbundle"'),
5418 '"hg unbundle"'),
5425 hint=_('use "hg debugapplystreamclonebundle"'))
5419 hint=_('use "hg debugapplystreamclonebundle"'))
5426 url = 'bundle:' + fname
5420 url = 'bundle:' + fname
5427 try:
5421 try:
5428 txnname = 'unbundle'
5422 txnname = 'unbundle'
5429 if not isinstance(gen, bundle2.unbundle20):
5423 if not isinstance(gen, bundle2.unbundle20):
5430 txnname = 'unbundle\n%s' % util.hidepassword(url)
5424 txnname = 'unbundle\n%s' % util.hidepassword(url)
5431 with repo.transaction(txnname) as tr:
5425 with repo.transaction(txnname) as tr:
5432 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5426 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5433 url=url)
5427 url=url)
5434 except error.BundleUnknownFeatureError as exc:
5428 except error.BundleUnknownFeatureError as exc:
5435 raise error.Abort(
5429 raise error.Abort(
5436 _('%s: unknown bundle feature, %s') % (fname, exc),
5430 _('%s: unknown bundle feature, %s') % (fname, exc),
5437 hint=_("see https://mercurial-scm.org/"
5431 hint=_("see https://mercurial-scm.org/"
5438 "wiki/BundleFeature for more "
5432 "wiki/BundleFeature for more "
5439 "information"))
5433 "information"))
5440 modheads = bundle2.combinechangegroupresults(op)
5434 modheads = bundle2.combinechangegroupresults(op)
5441
5435
5442 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5436 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5443
5437
5444 @command('^update|up|checkout|co',
5438 @command('^update|up|checkout|co',
5445 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5439 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5446 ('c', 'check', None, _('require clean working directory')),
5440 ('c', 'check', None, _('require clean working directory')),
5447 ('m', 'merge', None, _('merge uncommitted changes')),
5441 ('m', 'merge', None, _('merge uncommitted changes')),
5448 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5442 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5449 ('r', 'rev', '', _('revision'), _('REV'))
5443 ('r', 'rev', '', _('revision'), _('REV'))
5450 ] + mergetoolopts,
5444 ] + mergetoolopts,
5451 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5445 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5452 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
5446 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
5453 merge=None, tool=None):
5447 merge=None, tool=None):
5454 """update working directory (or switch revisions)
5448 """update working directory (or switch revisions)
5455
5449
5456 Update the repository's working directory to the specified
5450 Update the repository's working directory to the specified
5457 changeset. If no changeset is specified, update to the tip of the
5451 changeset. If no changeset is specified, update to the tip of the
5458 current named branch and move the active bookmark (see :hg:`help
5452 current named branch and move the active bookmark (see :hg:`help
5459 bookmarks`).
5453 bookmarks`).
5460
5454
5461 Update sets the working directory's parent revision to the specified
5455 Update sets the working directory's parent revision to the specified
5462 changeset (see :hg:`help parents`).
5456 changeset (see :hg:`help parents`).
5463
5457
5464 If the changeset is not a descendant or ancestor of the working
5458 If the changeset is not a descendant or ancestor of the working
5465 directory's parent and there are uncommitted changes, the update is
5459 directory's parent and there are uncommitted changes, the update is
5466 aborted. With the -c/--check option, the working directory is checked
5460 aborted. With the -c/--check option, the working directory is checked
5467 for uncommitted changes; if none are found, the working directory is
5461 for uncommitted changes; if none are found, the working directory is
5468 updated to the specified changeset.
5462 updated to the specified changeset.
5469
5463
5470 .. container:: verbose
5464 .. container:: verbose
5471
5465
5472 The -C/--clean, -c/--check, and -m/--merge options control what
5466 The -C/--clean, -c/--check, and -m/--merge options control what
5473 happens if the working directory contains uncommitted changes.
5467 happens if the working directory contains uncommitted changes.
5474 At most of one of them can be specified.
5468 At most of one of them can be specified.
5475
5469
5476 1. If no option is specified, and if
5470 1. If no option is specified, and if
5477 the requested changeset is an ancestor or descendant of
5471 the requested changeset is an ancestor or descendant of
5478 the working directory's parent, the uncommitted changes
5472 the working directory's parent, the uncommitted changes
5479 are merged into the requested changeset and the merged
5473 are merged into the requested changeset and the merged
5480 result is left uncommitted. If the requested changeset is
5474 result is left uncommitted. If the requested changeset is
5481 not an ancestor or descendant (that is, it is on another
5475 not an ancestor or descendant (that is, it is on another
5482 branch), the update is aborted and the uncommitted changes
5476 branch), the update is aborted and the uncommitted changes
5483 are preserved.
5477 are preserved.
5484
5478
5485 2. With the -m/--merge option, the update is allowed even if the
5479 2. With the -m/--merge option, the update is allowed even if the
5486 requested changeset is not an ancestor or descendant of
5480 requested changeset is not an ancestor or descendant of
5487 the working directory's parent.
5481 the working directory's parent.
5488
5482
5489 3. With the -c/--check option, the update is aborted and the
5483 3. With the -c/--check option, the update is aborted and the
5490 uncommitted changes are preserved.
5484 uncommitted changes are preserved.
5491
5485
5492 4. With the -C/--clean option, uncommitted changes are discarded and
5486 4. With the -C/--clean option, uncommitted changes are discarded and
5493 the working directory is updated to the requested changeset.
5487 the working directory is updated to the requested changeset.
5494
5488
5495 To cancel an uncommitted merge (and lose your changes), use
5489 To cancel an uncommitted merge (and lose your changes), use
5496 :hg:`update --clean .`.
5490 :hg:`update --clean .`.
5497
5491
5498 Use null as the changeset to remove the working directory (like
5492 Use null as the changeset to remove the working directory (like
5499 :hg:`clone -U`).
5493 :hg:`clone -U`).
5500
5494
5501 If you want to revert just one file to an older revision, use
5495 If you want to revert just one file to an older revision, use
5502 :hg:`revert [-r REV] NAME`.
5496 :hg:`revert [-r REV] NAME`.
5503
5497
5504 See :hg:`help dates` for a list of formats valid for -d/--date.
5498 See :hg:`help dates` for a list of formats valid for -d/--date.
5505
5499
5506 Returns 0 on success, 1 if there are unresolved files.
5500 Returns 0 on success, 1 if there are unresolved files.
5507 """
5501 """
5508 if rev and node:
5502 if rev and node:
5509 raise error.Abort(_("please specify just one revision"))
5503 raise error.Abort(_("please specify just one revision"))
5510
5504
5511 if ui.configbool('commands', 'update.requiredest'):
5505 if ui.configbool('commands', 'update.requiredest'):
5512 if not node and not rev and not date:
5506 if not node and not rev and not date:
5513 raise error.Abort(_('you must specify a destination'),
5507 raise error.Abort(_('you must specify a destination'),
5514 hint=_('for example: hg update ".::"'))
5508 hint=_('for example: hg update ".::"'))
5515
5509
5516 if rev is None or rev == '':
5510 if rev is None or rev == '':
5517 rev = node
5511 rev = node
5518
5512
5519 if date and rev is not None:
5513 if date and rev is not None:
5520 raise error.Abort(_("you can't specify a revision and a date"))
5514 raise error.Abort(_("you can't specify a revision and a date"))
5521
5515
5522 if len([x for x in (clean, check, merge) if x]) > 1:
5516 if len([x for x in (clean, check, merge) if x]) > 1:
5523 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5517 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5524 "or -m/--merge"))
5518 "or -m/--merge"))
5525
5519
5526 updatecheck = None
5520 updatecheck = None
5527 if check:
5521 if check:
5528 updatecheck = 'abort'
5522 updatecheck = 'abort'
5529 elif merge:
5523 elif merge:
5530 updatecheck = 'none'
5524 updatecheck = 'none'
5531
5525
5532 with repo.wlock():
5526 with repo.wlock():
5533 cmdutil.clearunfinished(repo)
5527 cmdutil.clearunfinished(repo)
5534
5528
5535 if date:
5529 if date:
5536 rev = cmdutil.finddate(ui, repo, date)
5530 rev = cmdutil.finddate(ui, repo, date)
5537
5531
5538 # if we defined a bookmark, we have to remember the original name
5532 # if we defined a bookmark, we have to remember the original name
5539 brev = rev
5533 brev = rev
5540 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5534 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5541 ctx = scmutil.revsingle(repo, rev, rev)
5535 ctx = scmutil.revsingle(repo, rev, rev)
5542 rev = ctx.rev()
5536 rev = ctx.rev()
5543 if ctx.hidden():
5537 if ctx.hidden():
5544 ui.warn(_("updating to a hidden changeset %s\n") % ctx.hex()[:12])
5538 ui.warn(_("updating to a hidden changeset %s\n") % ctx.hex()[:12])
5545
5539
5546 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
5540 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
5547
5541
5548 return hg.updatetotally(ui, repo, rev, brev, clean=clean,
5542 return hg.updatetotally(ui, repo, rev, brev, clean=clean,
5549 updatecheck=updatecheck)
5543 updatecheck=updatecheck)
5550
5544
5551 @command('verify', [])
5545 @command('verify', [])
5552 def verify(ui, repo):
5546 def verify(ui, repo):
5553 """verify the integrity of the repository
5547 """verify the integrity of the repository
5554
5548
5555 Verify the integrity of the current repository.
5549 Verify the integrity of the current repository.
5556
5550
5557 This will perform an extensive check of the repository's
5551 This will perform an extensive check of the repository's
5558 integrity, validating the hashes and checksums of each entry in
5552 integrity, validating the hashes and checksums of each entry in
5559 the changelog, manifest, and tracked files, as well as the
5553 the changelog, manifest, and tracked files, as well as the
5560 integrity of their crosslinks and indices.
5554 integrity of their crosslinks and indices.
5561
5555
5562 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5556 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5563 for more information about recovery from corruption of the
5557 for more information about recovery from corruption of the
5564 repository.
5558 repository.
5565
5559
5566 Returns 0 on success, 1 if errors are encountered.
5560 Returns 0 on success, 1 if errors are encountered.
5567 """
5561 """
5568 return hg.verify(repo)
5562 return hg.verify(repo)
5569
5563
5570 @command('version', [] + formatteropts, norepo=True, cmdtype=readonly)
5564 @command('version', [] + formatteropts, norepo=True, cmdtype=readonly)
5571 def version_(ui, **opts):
5565 def version_(ui, **opts):
5572 """output version and copyright information"""
5566 """output version and copyright information"""
5573 opts = pycompat.byteskwargs(opts)
5567 opts = pycompat.byteskwargs(opts)
5574 if ui.verbose:
5568 if ui.verbose:
5575 ui.pager('version')
5569 ui.pager('version')
5576 fm = ui.formatter("version", opts)
5570 fm = ui.formatter("version", opts)
5577 fm.startitem()
5571 fm.startitem()
5578 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5572 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5579 util.version())
5573 util.version())
5580 license = _(
5574 license = _(
5581 "(see https://mercurial-scm.org for more information)\n"
5575 "(see https://mercurial-scm.org for more information)\n"
5582 "\nCopyright (C) 2005-2017 Matt Mackall and others\n"
5576 "\nCopyright (C) 2005-2017 Matt Mackall and others\n"
5583 "This is free software; see the source for copying conditions. "
5577 "This is free software; see the source for copying conditions. "
5584 "There is NO\nwarranty; "
5578 "There is NO\nwarranty; "
5585 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5579 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5586 )
5580 )
5587 if not ui.quiet:
5581 if not ui.quiet:
5588 fm.plain(license)
5582 fm.plain(license)
5589
5583
5590 if ui.verbose:
5584 if ui.verbose:
5591 fm.plain(_("\nEnabled extensions:\n\n"))
5585 fm.plain(_("\nEnabled extensions:\n\n"))
5592 # format names and versions into columns
5586 # format names and versions into columns
5593 names = []
5587 names = []
5594 vers = []
5588 vers = []
5595 isinternals = []
5589 isinternals = []
5596 for name, module in extensions.extensions():
5590 for name, module in extensions.extensions():
5597 names.append(name)
5591 names.append(name)
5598 vers.append(extensions.moduleversion(module) or None)
5592 vers.append(extensions.moduleversion(module) or None)
5599 isinternals.append(extensions.ismoduleinternal(module))
5593 isinternals.append(extensions.ismoduleinternal(module))
5600 fn = fm.nested("extensions")
5594 fn = fm.nested("extensions")
5601 if names:
5595 if names:
5602 namefmt = " %%-%ds " % max(len(n) for n in names)
5596 namefmt = " %%-%ds " % max(len(n) for n in names)
5603 places = [_("external"), _("internal")]
5597 places = [_("external"), _("internal")]
5604 for n, v, p in zip(names, vers, isinternals):
5598 for n, v, p in zip(names, vers, isinternals):
5605 fn.startitem()
5599 fn.startitem()
5606 fn.condwrite(ui.verbose, "name", namefmt, n)
5600 fn.condwrite(ui.verbose, "name", namefmt, n)
5607 if ui.verbose:
5601 if ui.verbose:
5608 fn.plain("%s " % places[p])
5602 fn.plain("%s " % places[p])
5609 fn.data(bundled=p)
5603 fn.data(bundled=p)
5610 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5604 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5611 if ui.verbose:
5605 if ui.verbose:
5612 fn.plain("\n")
5606 fn.plain("\n")
5613 fn.end()
5607 fn.end()
5614 fm.end()
5608 fm.end()
5615
5609
5616 def loadcmdtable(ui, name, cmdtable):
5610 def loadcmdtable(ui, name, cmdtable):
5617 """Load command functions from specified cmdtable
5611 """Load command functions from specified cmdtable
5618 """
5612 """
5619 overrides = [cmd for cmd in cmdtable if cmd in table]
5613 overrides = [cmd for cmd in cmdtable if cmd in table]
5620 if overrides:
5614 if overrides:
5621 ui.warn(_("extension '%s' overrides commands: %s\n")
5615 ui.warn(_("extension '%s' overrides commands: %s\n")
5622 % (name, " ".join(overrides)))
5616 % (name, " ".join(overrides)))
5623 table.update(cmdtable)
5617 table.update(cmdtable)
General Comments 0
You need to be logged in to leave comments. Login now