##// END OF EJS Templates
patch: make extract() a context manager (API)...
Gregory Szorc -
r37639:5537d8f5 default
parent child Browse files
Show More
@@ -1,3234 +1,3232 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 os
11 import os
12 import re
12 import re
13 import tempfile
13 import tempfile
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 hex,
17 hex,
18 nullid,
18 nullid,
19 nullrev,
19 nullrev,
20 short,
20 short,
21 )
21 )
22
22
23 from . import (
23 from . import (
24 bookmarks,
24 bookmarks,
25 changelog,
25 changelog,
26 copies,
26 copies,
27 crecord as crecordmod,
27 crecord as crecordmod,
28 dirstateguard,
28 dirstateguard,
29 encoding,
29 encoding,
30 error,
30 error,
31 formatter,
31 formatter,
32 logcmdutil,
32 logcmdutil,
33 match as matchmod,
33 match as matchmod,
34 merge as mergemod,
34 merge as mergemod,
35 mergeutil,
35 mergeutil,
36 obsolete,
36 obsolete,
37 patch,
37 patch,
38 pathutil,
38 pathutil,
39 pycompat,
39 pycompat,
40 registrar,
40 registrar,
41 revlog,
41 revlog,
42 rewriteutil,
42 rewriteutil,
43 scmutil,
43 scmutil,
44 smartset,
44 smartset,
45 subrepoutil,
45 subrepoutil,
46 templatekw,
46 templatekw,
47 templater,
47 templater,
48 util,
48 util,
49 vfs as vfsmod,
49 vfs as vfsmod,
50 )
50 )
51
51
52 from .utils import (
52 from .utils import (
53 dateutil,
53 dateutil,
54 stringutil,
54 stringutil,
55 )
55 )
56
56
57 stringio = util.stringio
57 stringio = util.stringio
58
58
59 # templates of common command options
59 # templates of common command options
60
60
61 dryrunopts = [
61 dryrunopts = [
62 ('n', 'dry-run', None,
62 ('n', 'dry-run', None,
63 _('do not perform actions, just print output')),
63 _('do not perform actions, just print output')),
64 ]
64 ]
65
65
66 remoteopts = [
66 remoteopts = [
67 ('e', 'ssh', '',
67 ('e', 'ssh', '',
68 _('specify ssh command to use'), _('CMD')),
68 _('specify ssh command to use'), _('CMD')),
69 ('', 'remotecmd', '',
69 ('', 'remotecmd', '',
70 _('specify hg command to run on the remote side'), _('CMD')),
70 _('specify hg command to run on the remote side'), _('CMD')),
71 ('', 'insecure', None,
71 ('', 'insecure', None,
72 _('do not verify server certificate (ignoring web.cacerts config)')),
72 _('do not verify server certificate (ignoring web.cacerts config)')),
73 ]
73 ]
74
74
75 walkopts = [
75 walkopts = [
76 ('I', 'include', [],
76 ('I', 'include', [],
77 _('include names matching the given patterns'), _('PATTERN')),
77 _('include names matching the given patterns'), _('PATTERN')),
78 ('X', 'exclude', [],
78 ('X', 'exclude', [],
79 _('exclude names matching the given patterns'), _('PATTERN')),
79 _('exclude names matching the given patterns'), _('PATTERN')),
80 ]
80 ]
81
81
82 commitopts = [
82 commitopts = [
83 ('m', 'message', '',
83 ('m', 'message', '',
84 _('use text as commit message'), _('TEXT')),
84 _('use text as commit message'), _('TEXT')),
85 ('l', 'logfile', '',
85 ('l', 'logfile', '',
86 _('read commit message from file'), _('FILE')),
86 _('read commit message from file'), _('FILE')),
87 ]
87 ]
88
88
89 commitopts2 = [
89 commitopts2 = [
90 ('d', 'date', '',
90 ('d', 'date', '',
91 _('record the specified date as commit date'), _('DATE')),
91 _('record the specified date as commit date'), _('DATE')),
92 ('u', 'user', '',
92 ('u', 'user', '',
93 _('record the specified user as committer'), _('USER')),
93 _('record the specified user as committer'), _('USER')),
94 ]
94 ]
95
95
96 # hidden for now
96 # hidden for now
97 formatteropts = [
97 formatteropts = [
98 ('T', 'template', '',
98 ('T', 'template', '',
99 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
99 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
100 ]
100 ]
101
101
102 templateopts = [
102 templateopts = [
103 ('', 'style', '',
103 ('', 'style', '',
104 _('display using template map file (DEPRECATED)'), _('STYLE')),
104 _('display using template map file (DEPRECATED)'), _('STYLE')),
105 ('T', 'template', '',
105 ('T', 'template', '',
106 _('display with template'), _('TEMPLATE')),
106 _('display with template'), _('TEMPLATE')),
107 ]
107 ]
108
108
109 logopts = [
109 logopts = [
110 ('p', 'patch', None, _('show patch')),
110 ('p', 'patch', None, _('show patch')),
111 ('g', 'git', None, _('use git extended diff format')),
111 ('g', 'git', None, _('use git extended diff format')),
112 ('l', 'limit', '',
112 ('l', 'limit', '',
113 _('limit number of changes displayed'), _('NUM')),
113 _('limit number of changes displayed'), _('NUM')),
114 ('M', 'no-merges', None, _('do not show merges')),
114 ('M', 'no-merges', None, _('do not show merges')),
115 ('', 'stat', None, _('output diffstat-style summary of changes')),
115 ('', 'stat', None, _('output diffstat-style summary of changes')),
116 ('G', 'graph', None, _("show the revision DAG")),
116 ('G', 'graph', None, _("show the revision DAG")),
117 ] + templateopts
117 ] + templateopts
118
118
119 diffopts = [
119 diffopts = [
120 ('a', 'text', None, _('treat all files as text')),
120 ('a', 'text', None, _('treat all files as text')),
121 ('g', 'git', None, _('use git extended diff format')),
121 ('g', 'git', None, _('use git extended diff format')),
122 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
122 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
123 ('', 'nodates', None, _('omit dates from diff headers'))
123 ('', 'nodates', None, _('omit dates from diff headers'))
124 ]
124 ]
125
125
126 diffwsopts = [
126 diffwsopts = [
127 ('w', 'ignore-all-space', None,
127 ('w', 'ignore-all-space', None,
128 _('ignore white space when comparing lines')),
128 _('ignore white space when comparing lines')),
129 ('b', 'ignore-space-change', None,
129 ('b', 'ignore-space-change', None,
130 _('ignore changes in the amount of white space')),
130 _('ignore changes in the amount of white space')),
131 ('B', 'ignore-blank-lines', None,
131 ('B', 'ignore-blank-lines', None,
132 _('ignore changes whose lines are all blank')),
132 _('ignore changes whose lines are all blank')),
133 ('Z', 'ignore-space-at-eol', None,
133 ('Z', 'ignore-space-at-eol', None,
134 _('ignore changes in whitespace at EOL')),
134 _('ignore changes in whitespace at EOL')),
135 ]
135 ]
136
136
137 diffopts2 = [
137 diffopts2 = [
138 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
138 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
139 ('p', 'show-function', None, _('show which function each change is in')),
139 ('p', 'show-function', None, _('show which function each change is in')),
140 ('', 'reverse', None, _('produce a diff that undoes the changes')),
140 ('', 'reverse', None, _('produce a diff that undoes the changes')),
141 ] + diffwsopts + [
141 ] + diffwsopts + [
142 ('U', 'unified', '',
142 ('U', 'unified', '',
143 _('number of lines of context to show'), _('NUM')),
143 _('number of lines of context to show'), _('NUM')),
144 ('', 'stat', None, _('output diffstat-style summary of changes')),
144 ('', 'stat', None, _('output diffstat-style summary of changes')),
145 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
145 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
146 ]
146 ]
147
147
148 mergetoolopts = [
148 mergetoolopts = [
149 ('t', 'tool', '', _('specify merge tool')),
149 ('t', 'tool', '', _('specify merge tool')),
150 ]
150 ]
151
151
152 similarityopts = [
152 similarityopts = [
153 ('s', 'similarity', '',
153 ('s', 'similarity', '',
154 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
154 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
155 ]
155 ]
156
156
157 subrepoopts = [
157 subrepoopts = [
158 ('S', 'subrepos', None,
158 ('S', 'subrepos', None,
159 _('recurse into subrepositories'))
159 _('recurse into subrepositories'))
160 ]
160 ]
161
161
162 debugrevlogopts = [
162 debugrevlogopts = [
163 ('c', 'changelog', False, _('open changelog')),
163 ('c', 'changelog', False, _('open changelog')),
164 ('m', 'manifest', False, _('open manifest')),
164 ('m', 'manifest', False, _('open manifest')),
165 ('', 'dir', '', _('open directory manifest')),
165 ('', 'dir', '', _('open directory manifest')),
166 ]
166 ]
167
167
168 # special string such that everything below this line will be ingored in the
168 # special string such that everything below this line will be ingored in the
169 # editor text
169 # editor text
170 _linebelow = "^HG: ------------------------ >8 ------------------------$"
170 _linebelow = "^HG: ------------------------ >8 ------------------------$"
171
171
172 def ishunk(x):
172 def ishunk(x):
173 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
173 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
174 return isinstance(x, hunkclasses)
174 return isinstance(x, hunkclasses)
175
175
176 def newandmodified(chunks, originalchunks):
176 def newandmodified(chunks, originalchunks):
177 newlyaddedandmodifiedfiles = set()
177 newlyaddedandmodifiedfiles = set()
178 for chunk in chunks:
178 for chunk in chunks:
179 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
179 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
180 originalchunks:
180 originalchunks:
181 newlyaddedandmodifiedfiles.add(chunk.header.filename())
181 newlyaddedandmodifiedfiles.add(chunk.header.filename())
182 return newlyaddedandmodifiedfiles
182 return newlyaddedandmodifiedfiles
183
183
184 def parsealiases(cmd):
184 def parsealiases(cmd):
185 return cmd.lstrip("^").split("|")
185 return cmd.lstrip("^").split("|")
186
186
187 def setupwrapcolorwrite(ui):
187 def setupwrapcolorwrite(ui):
188 # wrap ui.write so diff output can be labeled/colorized
188 # wrap ui.write so diff output can be labeled/colorized
189 def wrapwrite(orig, *args, **kw):
189 def wrapwrite(orig, *args, **kw):
190 label = kw.pop(r'label', '')
190 label = kw.pop(r'label', '')
191 for chunk, l in patch.difflabel(lambda: args):
191 for chunk, l in patch.difflabel(lambda: args):
192 orig(chunk, label=label + l)
192 orig(chunk, label=label + l)
193
193
194 oldwrite = ui.write
194 oldwrite = ui.write
195 def wrap(*args, **kwargs):
195 def wrap(*args, **kwargs):
196 return wrapwrite(oldwrite, *args, **kwargs)
196 return wrapwrite(oldwrite, *args, **kwargs)
197 setattr(ui, 'write', wrap)
197 setattr(ui, 'write', wrap)
198 return oldwrite
198 return oldwrite
199
199
200 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
200 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
201 if usecurses:
201 if usecurses:
202 if testfile:
202 if testfile:
203 recordfn = crecordmod.testdecorator(testfile,
203 recordfn = crecordmod.testdecorator(testfile,
204 crecordmod.testchunkselector)
204 crecordmod.testchunkselector)
205 else:
205 else:
206 recordfn = crecordmod.chunkselector
206 recordfn = crecordmod.chunkselector
207
207
208 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
208 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
209
209
210 else:
210 else:
211 return patch.filterpatch(ui, originalhunks, operation)
211 return patch.filterpatch(ui, originalhunks, operation)
212
212
213 def recordfilter(ui, originalhunks, operation=None):
213 def recordfilter(ui, originalhunks, operation=None):
214 """ Prompts the user to filter the originalhunks and return a list of
214 """ Prompts the user to filter the originalhunks and return a list of
215 selected hunks.
215 selected hunks.
216 *operation* is used for to build ui messages to indicate the user what
216 *operation* is used for to build ui messages to indicate the user what
217 kind of filtering they are doing: reverting, committing, shelving, etc.
217 kind of filtering they are doing: reverting, committing, shelving, etc.
218 (see patch.filterpatch).
218 (see patch.filterpatch).
219 """
219 """
220 usecurses = crecordmod.checkcurses(ui)
220 usecurses = crecordmod.checkcurses(ui)
221 testfile = ui.config('experimental', 'crecordtest')
221 testfile = ui.config('experimental', 'crecordtest')
222 oldwrite = setupwrapcolorwrite(ui)
222 oldwrite = setupwrapcolorwrite(ui)
223 try:
223 try:
224 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
224 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
225 testfile, operation)
225 testfile, operation)
226 finally:
226 finally:
227 ui.write = oldwrite
227 ui.write = oldwrite
228 return newchunks, newopts
228 return newchunks, newopts
229
229
230 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
230 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
231 filterfn, *pats, **opts):
231 filterfn, *pats, **opts):
232 opts = pycompat.byteskwargs(opts)
232 opts = pycompat.byteskwargs(opts)
233 if not ui.interactive():
233 if not ui.interactive():
234 if cmdsuggest:
234 if cmdsuggest:
235 msg = _('running non-interactively, use %s instead') % cmdsuggest
235 msg = _('running non-interactively, use %s instead') % cmdsuggest
236 else:
236 else:
237 msg = _('running non-interactively')
237 msg = _('running non-interactively')
238 raise error.Abort(msg)
238 raise error.Abort(msg)
239
239
240 # make sure username is set before going interactive
240 # make sure username is set before going interactive
241 if not opts.get('user'):
241 if not opts.get('user'):
242 ui.username() # raise exception, username not provided
242 ui.username() # raise exception, username not provided
243
243
244 def recordfunc(ui, repo, message, match, opts):
244 def recordfunc(ui, repo, message, match, opts):
245 """This is generic record driver.
245 """This is generic record driver.
246
246
247 Its job is to interactively filter local changes, and
247 Its job is to interactively filter local changes, and
248 accordingly prepare working directory into a state in which the
248 accordingly prepare working directory into a state in which the
249 job can be delegated to a non-interactive commit command such as
249 job can be delegated to a non-interactive commit command such as
250 'commit' or 'qrefresh'.
250 'commit' or 'qrefresh'.
251
251
252 After the actual job is done by non-interactive command, the
252 After the actual job is done by non-interactive command, the
253 working directory is restored to its original state.
253 working directory is restored to its original state.
254
254
255 In the end we'll record interesting changes, and everything else
255 In the end we'll record interesting changes, and everything else
256 will be left in place, so the user can continue working.
256 will be left in place, so the user can continue working.
257 """
257 """
258
258
259 checkunfinished(repo, commit=True)
259 checkunfinished(repo, commit=True)
260 wctx = repo[None]
260 wctx = repo[None]
261 merge = len(wctx.parents()) > 1
261 merge = len(wctx.parents()) > 1
262 if merge:
262 if merge:
263 raise error.Abort(_('cannot partially commit a merge '
263 raise error.Abort(_('cannot partially commit a merge '
264 '(use "hg commit" instead)'))
264 '(use "hg commit" instead)'))
265
265
266 def fail(f, msg):
266 def fail(f, msg):
267 raise error.Abort('%s: %s' % (f, msg))
267 raise error.Abort('%s: %s' % (f, msg))
268
268
269 force = opts.get('force')
269 force = opts.get('force')
270 if not force:
270 if not force:
271 vdirs = []
271 vdirs = []
272 match.explicitdir = vdirs.append
272 match.explicitdir = vdirs.append
273 match.bad = fail
273 match.bad = fail
274
274
275 status = repo.status(match=match)
275 status = repo.status(match=match)
276 if not force:
276 if not force:
277 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
277 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
278 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
278 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
279 diffopts.nodates = True
279 diffopts.nodates = True
280 diffopts.git = True
280 diffopts.git = True
281 diffopts.showfunc = True
281 diffopts.showfunc = True
282 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
282 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
283 originalchunks = patch.parsepatch(originaldiff)
283 originalchunks = patch.parsepatch(originaldiff)
284
284
285 # 1. filter patch, since we are intending to apply subset of it
285 # 1. filter patch, since we are intending to apply subset of it
286 try:
286 try:
287 chunks, newopts = filterfn(ui, originalchunks)
287 chunks, newopts = filterfn(ui, originalchunks)
288 except error.PatchError as err:
288 except error.PatchError as err:
289 raise error.Abort(_('error parsing patch: %s') % err)
289 raise error.Abort(_('error parsing patch: %s') % err)
290 opts.update(newopts)
290 opts.update(newopts)
291
291
292 # We need to keep a backup of files that have been newly added and
292 # We need to keep a backup of files that have been newly added and
293 # modified during the recording process because there is a previous
293 # modified during the recording process because there is a previous
294 # version without the edit in the workdir
294 # version without the edit in the workdir
295 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
295 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
296 contenders = set()
296 contenders = set()
297 for h in chunks:
297 for h in chunks:
298 try:
298 try:
299 contenders.update(set(h.files()))
299 contenders.update(set(h.files()))
300 except AttributeError:
300 except AttributeError:
301 pass
301 pass
302
302
303 changed = status.modified + status.added + status.removed
303 changed = status.modified + status.added + status.removed
304 newfiles = [f for f in changed if f in contenders]
304 newfiles = [f for f in changed if f in contenders]
305 if not newfiles:
305 if not newfiles:
306 ui.status(_('no changes to record\n'))
306 ui.status(_('no changes to record\n'))
307 return 0
307 return 0
308
308
309 modified = set(status.modified)
309 modified = set(status.modified)
310
310
311 # 2. backup changed files, so we can restore them in the end
311 # 2. backup changed files, so we can restore them in the end
312
312
313 if backupall:
313 if backupall:
314 tobackup = changed
314 tobackup = changed
315 else:
315 else:
316 tobackup = [f for f in newfiles if f in modified or f in \
316 tobackup = [f for f in newfiles if f in modified or f in \
317 newlyaddedandmodifiedfiles]
317 newlyaddedandmodifiedfiles]
318 backups = {}
318 backups = {}
319 if tobackup:
319 if tobackup:
320 backupdir = repo.vfs.join('record-backups')
320 backupdir = repo.vfs.join('record-backups')
321 try:
321 try:
322 os.mkdir(backupdir)
322 os.mkdir(backupdir)
323 except OSError as err:
323 except OSError as err:
324 if err.errno != errno.EEXIST:
324 if err.errno != errno.EEXIST:
325 raise
325 raise
326 try:
326 try:
327 # backup continues
327 # backup continues
328 for f in tobackup:
328 for f in tobackup:
329 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
329 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
330 dir=backupdir)
330 dir=backupdir)
331 os.close(fd)
331 os.close(fd)
332 ui.debug('backup %r as %r\n' % (f, tmpname))
332 ui.debug('backup %r as %r\n' % (f, tmpname))
333 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
333 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
334 backups[f] = tmpname
334 backups[f] = tmpname
335
335
336 fp = stringio()
336 fp = stringio()
337 for c in chunks:
337 for c in chunks:
338 fname = c.filename()
338 fname = c.filename()
339 if fname in backups:
339 if fname in backups:
340 c.write(fp)
340 c.write(fp)
341 dopatch = fp.tell()
341 dopatch = fp.tell()
342 fp.seek(0)
342 fp.seek(0)
343
343
344 # 2.5 optionally review / modify patch in text editor
344 # 2.5 optionally review / modify patch in text editor
345 if opts.get('review', False):
345 if opts.get('review', False):
346 patchtext = (crecordmod.diffhelptext
346 patchtext = (crecordmod.diffhelptext
347 + crecordmod.patchhelptext
347 + crecordmod.patchhelptext
348 + fp.read())
348 + fp.read())
349 reviewedpatch = ui.edit(patchtext, "",
349 reviewedpatch = ui.edit(patchtext, "",
350 action="diff",
350 action="diff",
351 repopath=repo.path)
351 repopath=repo.path)
352 fp.truncate(0)
352 fp.truncate(0)
353 fp.write(reviewedpatch)
353 fp.write(reviewedpatch)
354 fp.seek(0)
354 fp.seek(0)
355
355
356 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
356 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
357 # 3a. apply filtered patch to clean repo (clean)
357 # 3a. apply filtered patch to clean repo (clean)
358 if backups:
358 if backups:
359 # Equivalent to hg.revert
359 # Equivalent to hg.revert
360 m = scmutil.matchfiles(repo, backups.keys())
360 m = scmutil.matchfiles(repo, backups.keys())
361 mergemod.update(repo, repo.dirstate.p1(),
361 mergemod.update(repo, repo.dirstate.p1(),
362 False, True, matcher=m)
362 False, True, matcher=m)
363
363
364 # 3b. (apply)
364 # 3b. (apply)
365 if dopatch:
365 if dopatch:
366 try:
366 try:
367 ui.debug('applying patch\n')
367 ui.debug('applying patch\n')
368 ui.debug(fp.getvalue())
368 ui.debug(fp.getvalue())
369 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
369 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
370 except error.PatchError as err:
370 except error.PatchError as err:
371 raise error.Abort(pycompat.bytestr(err))
371 raise error.Abort(pycompat.bytestr(err))
372 del fp
372 del fp
373
373
374 # 4. We prepared working directory according to filtered
374 # 4. We prepared working directory according to filtered
375 # patch. Now is the time to delegate the job to
375 # patch. Now is the time to delegate the job to
376 # commit/qrefresh or the like!
376 # commit/qrefresh or the like!
377
377
378 # Make all of the pathnames absolute.
378 # Make all of the pathnames absolute.
379 newfiles = [repo.wjoin(nf) for nf in newfiles]
379 newfiles = [repo.wjoin(nf) for nf in newfiles]
380 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
380 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
381 finally:
381 finally:
382 # 5. finally restore backed-up files
382 # 5. finally restore backed-up files
383 try:
383 try:
384 dirstate = repo.dirstate
384 dirstate = repo.dirstate
385 for realname, tmpname in backups.iteritems():
385 for realname, tmpname in backups.iteritems():
386 ui.debug('restoring %r to %r\n' % (tmpname, realname))
386 ui.debug('restoring %r to %r\n' % (tmpname, realname))
387
387
388 if dirstate[realname] == 'n':
388 if dirstate[realname] == 'n':
389 # without normallookup, restoring timestamp
389 # without normallookup, restoring timestamp
390 # may cause partially committed files
390 # may cause partially committed files
391 # to be treated as unmodified
391 # to be treated as unmodified
392 dirstate.normallookup(realname)
392 dirstate.normallookup(realname)
393
393
394 # copystat=True here and above are a hack to trick any
394 # copystat=True here and above are a hack to trick any
395 # editors that have f open that we haven't modified them.
395 # editors that have f open that we haven't modified them.
396 #
396 #
397 # Also note that this racy as an editor could notice the
397 # Also note that this racy as an editor could notice the
398 # file's mtime before we've finished writing it.
398 # file's mtime before we've finished writing it.
399 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
399 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
400 os.unlink(tmpname)
400 os.unlink(tmpname)
401 if tobackup:
401 if tobackup:
402 os.rmdir(backupdir)
402 os.rmdir(backupdir)
403 except OSError:
403 except OSError:
404 pass
404 pass
405
405
406 def recordinwlock(ui, repo, message, match, opts):
406 def recordinwlock(ui, repo, message, match, opts):
407 with repo.wlock():
407 with repo.wlock():
408 return recordfunc(ui, repo, message, match, opts)
408 return recordfunc(ui, repo, message, match, opts)
409
409
410 return commit(ui, repo, recordinwlock, pats, opts)
410 return commit(ui, repo, recordinwlock, pats, opts)
411
411
412 class dirnode(object):
412 class dirnode(object):
413 """
413 """
414 Represent a directory in user working copy with information required for
414 Represent a directory in user working copy with information required for
415 the purpose of tersing its status.
415 the purpose of tersing its status.
416
416
417 path is the path to the directory
417 path is the path to the directory
418
418
419 statuses is a set of statuses of all files in this directory (this includes
419 statuses is a set of statuses of all files in this directory (this includes
420 all the files in all the subdirectories too)
420 all the files in all the subdirectories too)
421
421
422 files is a list of files which are direct child of this directory
422 files is a list of files which are direct child of this directory
423
423
424 subdirs is a dictionary of sub-directory name as the key and it's own
424 subdirs is a dictionary of sub-directory name as the key and it's own
425 dirnode object as the value
425 dirnode object as the value
426 """
426 """
427
427
428 def __init__(self, dirpath):
428 def __init__(self, dirpath):
429 self.path = dirpath
429 self.path = dirpath
430 self.statuses = set([])
430 self.statuses = set([])
431 self.files = []
431 self.files = []
432 self.subdirs = {}
432 self.subdirs = {}
433
433
434 def _addfileindir(self, filename, status):
434 def _addfileindir(self, filename, status):
435 """Add a file in this directory as a direct child."""
435 """Add a file in this directory as a direct child."""
436 self.files.append((filename, status))
436 self.files.append((filename, status))
437
437
438 def addfile(self, filename, status):
438 def addfile(self, filename, status):
439 """
439 """
440 Add a file to this directory or to its direct parent directory.
440 Add a file to this directory or to its direct parent directory.
441
441
442 If the file is not direct child of this directory, we traverse to the
442 If the file is not direct child of this directory, we traverse to the
443 directory of which this file is a direct child of and add the file
443 directory of which this file is a direct child of and add the file
444 there.
444 there.
445 """
445 """
446
446
447 # the filename contains a path separator, it means it's not the direct
447 # the filename contains a path separator, it means it's not the direct
448 # child of this directory
448 # child of this directory
449 if '/' in filename:
449 if '/' in filename:
450 subdir, filep = filename.split('/', 1)
450 subdir, filep = filename.split('/', 1)
451
451
452 # does the dirnode object for subdir exists
452 # does the dirnode object for subdir exists
453 if subdir not in self.subdirs:
453 if subdir not in self.subdirs:
454 subdirpath = os.path.join(self.path, subdir)
454 subdirpath = os.path.join(self.path, subdir)
455 self.subdirs[subdir] = dirnode(subdirpath)
455 self.subdirs[subdir] = dirnode(subdirpath)
456
456
457 # try adding the file in subdir
457 # try adding the file in subdir
458 self.subdirs[subdir].addfile(filep, status)
458 self.subdirs[subdir].addfile(filep, status)
459
459
460 else:
460 else:
461 self._addfileindir(filename, status)
461 self._addfileindir(filename, status)
462
462
463 if status not in self.statuses:
463 if status not in self.statuses:
464 self.statuses.add(status)
464 self.statuses.add(status)
465
465
466 def iterfilepaths(self):
466 def iterfilepaths(self):
467 """Yield (status, path) for files directly under this directory."""
467 """Yield (status, path) for files directly under this directory."""
468 for f, st in self.files:
468 for f, st in self.files:
469 yield st, os.path.join(self.path, f)
469 yield st, os.path.join(self.path, f)
470
470
471 def tersewalk(self, terseargs):
471 def tersewalk(self, terseargs):
472 """
472 """
473 Yield (status, path) obtained by processing the status of this
473 Yield (status, path) obtained by processing the status of this
474 dirnode.
474 dirnode.
475
475
476 terseargs is the string of arguments passed by the user with `--terse`
476 terseargs is the string of arguments passed by the user with `--terse`
477 flag.
477 flag.
478
478
479 Following are the cases which can happen:
479 Following are the cases which can happen:
480
480
481 1) All the files in the directory (including all the files in its
481 1) All the files in the directory (including all the files in its
482 subdirectories) share the same status and the user has asked us to terse
482 subdirectories) share the same status and the user has asked us to terse
483 that status. -> yield (status, dirpath)
483 that status. -> yield (status, dirpath)
484
484
485 2) Otherwise, we do following:
485 2) Otherwise, we do following:
486
486
487 a) Yield (status, filepath) for all the files which are in this
487 a) Yield (status, filepath) for all the files which are in this
488 directory (only the ones in this directory, not the subdirs)
488 directory (only the ones in this directory, not the subdirs)
489
489
490 b) Recurse the function on all the subdirectories of this
490 b) Recurse the function on all the subdirectories of this
491 directory
491 directory
492 """
492 """
493
493
494 if len(self.statuses) == 1:
494 if len(self.statuses) == 1:
495 onlyst = self.statuses.pop()
495 onlyst = self.statuses.pop()
496
496
497 # Making sure we terse only when the status abbreviation is
497 # Making sure we terse only when the status abbreviation is
498 # passed as terse argument
498 # passed as terse argument
499 if onlyst in terseargs:
499 if onlyst in terseargs:
500 yield onlyst, self.path + pycompat.ossep
500 yield onlyst, self.path + pycompat.ossep
501 return
501 return
502
502
503 # add the files to status list
503 # add the files to status list
504 for st, fpath in self.iterfilepaths():
504 for st, fpath in self.iterfilepaths():
505 yield st, fpath
505 yield st, fpath
506
506
507 #recurse on the subdirs
507 #recurse on the subdirs
508 for dirobj in self.subdirs.values():
508 for dirobj in self.subdirs.values():
509 for st, fpath in dirobj.tersewalk(terseargs):
509 for st, fpath in dirobj.tersewalk(terseargs):
510 yield st, fpath
510 yield st, fpath
511
511
512 def tersedir(statuslist, terseargs):
512 def tersedir(statuslist, terseargs):
513 """
513 """
514 Terse the status if all the files in a directory shares the same status.
514 Terse the status if all the files in a directory shares the same status.
515
515
516 statuslist is scmutil.status() object which contains a list of files for
516 statuslist is scmutil.status() object which contains a list of files for
517 each status.
517 each status.
518 terseargs is string which is passed by the user as the argument to `--terse`
518 terseargs is string which is passed by the user as the argument to `--terse`
519 flag.
519 flag.
520
520
521 The function makes a tree of objects of dirnode class, and at each node it
521 The function makes a tree of objects of dirnode class, and at each node it
522 stores the information required to know whether we can terse a certain
522 stores the information required to know whether we can terse a certain
523 directory or not.
523 directory or not.
524 """
524 """
525 # the order matters here as that is used to produce final list
525 # the order matters here as that is used to produce final list
526 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
526 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
527
527
528 # checking the argument validity
528 # checking the argument validity
529 for s in pycompat.bytestr(terseargs):
529 for s in pycompat.bytestr(terseargs):
530 if s not in allst:
530 if s not in allst:
531 raise error.Abort(_("'%s' not recognized") % s)
531 raise error.Abort(_("'%s' not recognized") % s)
532
532
533 # creating a dirnode object for the root of the repo
533 # creating a dirnode object for the root of the repo
534 rootobj = dirnode('')
534 rootobj = dirnode('')
535 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
535 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
536 'ignored', 'removed')
536 'ignored', 'removed')
537
537
538 tersedict = {}
538 tersedict = {}
539 for attrname in pstatus:
539 for attrname in pstatus:
540 statuschar = attrname[0:1]
540 statuschar = attrname[0:1]
541 for f in getattr(statuslist, attrname):
541 for f in getattr(statuslist, attrname):
542 rootobj.addfile(f, statuschar)
542 rootobj.addfile(f, statuschar)
543 tersedict[statuschar] = []
543 tersedict[statuschar] = []
544
544
545 # we won't be tersing the root dir, so add files in it
545 # we won't be tersing the root dir, so add files in it
546 for st, fpath in rootobj.iterfilepaths():
546 for st, fpath in rootobj.iterfilepaths():
547 tersedict[st].append(fpath)
547 tersedict[st].append(fpath)
548
548
549 # process each sub-directory and build tersedict
549 # process each sub-directory and build tersedict
550 for subdir in rootobj.subdirs.values():
550 for subdir in rootobj.subdirs.values():
551 for st, f in subdir.tersewalk(terseargs):
551 for st, f in subdir.tersewalk(terseargs):
552 tersedict[st].append(f)
552 tersedict[st].append(f)
553
553
554 tersedlist = []
554 tersedlist = []
555 for st in allst:
555 for st in allst:
556 tersedict[st].sort()
556 tersedict[st].sort()
557 tersedlist.append(tersedict[st])
557 tersedlist.append(tersedict[st])
558
558
559 return tersedlist
559 return tersedlist
560
560
561 def _commentlines(raw):
561 def _commentlines(raw):
562 '''Surround lineswith a comment char and a new line'''
562 '''Surround lineswith a comment char and a new line'''
563 lines = raw.splitlines()
563 lines = raw.splitlines()
564 commentedlines = ['# %s' % line for line in lines]
564 commentedlines = ['# %s' % line for line in lines]
565 return '\n'.join(commentedlines) + '\n'
565 return '\n'.join(commentedlines) + '\n'
566
566
567 def _conflictsmsg(repo):
567 def _conflictsmsg(repo):
568 mergestate = mergemod.mergestate.read(repo)
568 mergestate = mergemod.mergestate.read(repo)
569 if not mergestate.active():
569 if not mergestate.active():
570 return
570 return
571
571
572 m = scmutil.match(repo[None])
572 m = scmutil.match(repo[None])
573 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
573 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
574 if unresolvedlist:
574 if unresolvedlist:
575 mergeliststr = '\n'.join(
575 mergeliststr = '\n'.join(
576 [' %s' % util.pathto(repo.root, pycompat.getcwd(), path)
576 [' %s' % util.pathto(repo.root, pycompat.getcwd(), path)
577 for path in unresolvedlist])
577 for path in unresolvedlist])
578 msg = _('''Unresolved merge conflicts:
578 msg = _('''Unresolved merge conflicts:
579
579
580 %s
580 %s
581
581
582 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
582 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
583 else:
583 else:
584 msg = _('No unresolved merge conflicts.')
584 msg = _('No unresolved merge conflicts.')
585
585
586 return _commentlines(msg)
586 return _commentlines(msg)
587
587
588 def _helpmessage(continuecmd, abortcmd):
588 def _helpmessage(continuecmd, abortcmd):
589 msg = _('To continue: %s\n'
589 msg = _('To continue: %s\n'
590 'To abort: %s') % (continuecmd, abortcmd)
590 'To abort: %s') % (continuecmd, abortcmd)
591 return _commentlines(msg)
591 return _commentlines(msg)
592
592
593 def _rebasemsg():
593 def _rebasemsg():
594 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
594 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
595
595
596 def _histeditmsg():
596 def _histeditmsg():
597 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
597 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
598
598
599 def _unshelvemsg():
599 def _unshelvemsg():
600 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
600 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
601
601
602 def _updatecleanmsg(dest=None):
602 def _updatecleanmsg(dest=None):
603 warning = _('warning: this will discard uncommitted changes')
603 warning = _('warning: this will discard uncommitted changes')
604 return 'hg update --clean %s (%s)' % (dest or '.', warning)
604 return 'hg update --clean %s (%s)' % (dest or '.', warning)
605
605
606 def _graftmsg():
606 def _graftmsg():
607 # tweakdefaults requires `update` to have a rev hence the `.`
607 # tweakdefaults requires `update` to have a rev hence the `.`
608 return _helpmessage('hg graft --continue', _updatecleanmsg())
608 return _helpmessage('hg graft --continue', _updatecleanmsg())
609
609
610 def _mergemsg():
610 def _mergemsg():
611 # tweakdefaults requires `update` to have a rev hence the `.`
611 # tweakdefaults requires `update` to have a rev hence the `.`
612 return _helpmessage('hg commit', _updatecleanmsg())
612 return _helpmessage('hg commit', _updatecleanmsg())
613
613
614 def _bisectmsg():
614 def _bisectmsg():
615 msg = _('To mark the changeset good: hg bisect --good\n'
615 msg = _('To mark the changeset good: hg bisect --good\n'
616 'To mark the changeset bad: hg bisect --bad\n'
616 'To mark the changeset bad: hg bisect --bad\n'
617 'To abort: hg bisect --reset\n')
617 'To abort: hg bisect --reset\n')
618 return _commentlines(msg)
618 return _commentlines(msg)
619
619
620 def fileexistspredicate(filename):
620 def fileexistspredicate(filename):
621 return lambda repo: repo.vfs.exists(filename)
621 return lambda repo: repo.vfs.exists(filename)
622
622
623 def _mergepredicate(repo):
623 def _mergepredicate(repo):
624 return len(repo[None].parents()) > 1
624 return len(repo[None].parents()) > 1
625
625
626 STATES = (
626 STATES = (
627 # (state, predicate to detect states, helpful message function)
627 # (state, predicate to detect states, helpful message function)
628 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
628 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
629 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
629 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
630 ('graft', fileexistspredicate('graftstate'), _graftmsg),
630 ('graft', fileexistspredicate('graftstate'), _graftmsg),
631 ('unshelve', fileexistspredicate('unshelverebasestate'), _unshelvemsg),
631 ('unshelve', fileexistspredicate('unshelverebasestate'), _unshelvemsg),
632 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
632 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
633 # The merge state is part of a list that will be iterated over.
633 # The merge state is part of a list that will be iterated over.
634 # They need to be last because some of the other unfinished states may also
634 # They need to be last because some of the other unfinished states may also
635 # be in a merge or update state (eg. rebase, histedit, graft, etc).
635 # be in a merge or update state (eg. rebase, histedit, graft, etc).
636 # We want those to have priority.
636 # We want those to have priority.
637 ('merge', _mergepredicate, _mergemsg),
637 ('merge', _mergepredicate, _mergemsg),
638 )
638 )
639
639
640 def _getrepostate(repo):
640 def _getrepostate(repo):
641 # experimental config: commands.status.skipstates
641 # experimental config: commands.status.skipstates
642 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
642 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
643 for state, statedetectionpredicate, msgfn in STATES:
643 for state, statedetectionpredicate, msgfn in STATES:
644 if state in skip:
644 if state in skip:
645 continue
645 continue
646 if statedetectionpredicate(repo):
646 if statedetectionpredicate(repo):
647 return (state, statedetectionpredicate, msgfn)
647 return (state, statedetectionpredicate, msgfn)
648
648
649 def morestatus(repo, fm):
649 def morestatus(repo, fm):
650 statetuple = _getrepostate(repo)
650 statetuple = _getrepostate(repo)
651 label = 'status.morestatus'
651 label = 'status.morestatus'
652 if statetuple:
652 if statetuple:
653 fm.startitem()
653 fm.startitem()
654 state, statedetectionpredicate, helpfulmsg = statetuple
654 state, statedetectionpredicate, helpfulmsg = statetuple
655 statemsg = _('The repository is in an unfinished *%s* state.') % state
655 statemsg = _('The repository is in an unfinished *%s* state.') % state
656 fm.write('statemsg', '%s\n', _commentlines(statemsg), label=label)
656 fm.write('statemsg', '%s\n', _commentlines(statemsg), label=label)
657 conmsg = _conflictsmsg(repo)
657 conmsg = _conflictsmsg(repo)
658 if conmsg:
658 if conmsg:
659 fm.write('conflictsmsg', '%s\n', conmsg, label=label)
659 fm.write('conflictsmsg', '%s\n', conmsg, label=label)
660 if helpfulmsg:
660 if helpfulmsg:
661 helpmsg = helpfulmsg()
661 helpmsg = helpfulmsg()
662 fm.write('helpmsg', '%s\n', helpmsg, label=label)
662 fm.write('helpmsg', '%s\n', helpmsg, label=label)
663
663
664 def findpossible(cmd, table, strict=False):
664 def findpossible(cmd, table, strict=False):
665 """
665 """
666 Return cmd -> (aliases, command table entry)
666 Return cmd -> (aliases, command table entry)
667 for each matching command.
667 for each matching command.
668 Return debug commands (or their aliases) only if no normal command matches.
668 Return debug commands (or their aliases) only if no normal command matches.
669 """
669 """
670 choice = {}
670 choice = {}
671 debugchoice = {}
671 debugchoice = {}
672
672
673 if cmd in table:
673 if cmd in table:
674 # short-circuit exact matches, "log" alias beats "^log|history"
674 # short-circuit exact matches, "log" alias beats "^log|history"
675 keys = [cmd]
675 keys = [cmd]
676 else:
676 else:
677 keys = table.keys()
677 keys = table.keys()
678
678
679 allcmds = []
679 allcmds = []
680 for e in keys:
680 for e in keys:
681 aliases = parsealiases(e)
681 aliases = parsealiases(e)
682 allcmds.extend(aliases)
682 allcmds.extend(aliases)
683 found = None
683 found = None
684 if cmd in aliases:
684 if cmd in aliases:
685 found = cmd
685 found = cmd
686 elif not strict:
686 elif not strict:
687 for a in aliases:
687 for a in aliases:
688 if a.startswith(cmd):
688 if a.startswith(cmd):
689 found = a
689 found = a
690 break
690 break
691 if found is not None:
691 if found is not None:
692 if aliases[0].startswith("debug") or found.startswith("debug"):
692 if aliases[0].startswith("debug") or found.startswith("debug"):
693 debugchoice[found] = (aliases, table[e])
693 debugchoice[found] = (aliases, table[e])
694 else:
694 else:
695 choice[found] = (aliases, table[e])
695 choice[found] = (aliases, table[e])
696
696
697 if not choice and debugchoice:
697 if not choice and debugchoice:
698 choice = debugchoice
698 choice = debugchoice
699
699
700 return choice, allcmds
700 return choice, allcmds
701
701
702 def findcmd(cmd, table, strict=True):
702 def findcmd(cmd, table, strict=True):
703 """Return (aliases, command table entry) for command string."""
703 """Return (aliases, command table entry) for command string."""
704 choice, allcmds = findpossible(cmd, table, strict)
704 choice, allcmds = findpossible(cmd, table, strict)
705
705
706 if cmd in choice:
706 if cmd in choice:
707 return choice[cmd]
707 return choice[cmd]
708
708
709 if len(choice) > 1:
709 if len(choice) > 1:
710 clist = sorted(choice)
710 clist = sorted(choice)
711 raise error.AmbiguousCommand(cmd, clist)
711 raise error.AmbiguousCommand(cmd, clist)
712
712
713 if choice:
713 if choice:
714 return list(choice.values())[0]
714 return list(choice.values())[0]
715
715
716 raise error.UnknownCommand(cmd, allcmds)
716 raise error.UnknownCommand(cmd, allcmds)
717
717
718 def changebranch(ui, repo, revs, label):
718 def changebranch(ui, repo, revs, label):
719 """ Change the branch name of given revs to label """
719 """ Change the branch name of given revs to label """
720
720
721 with repo.wlock(), repo.lock(), repo.transaction('branches'):
721 with repo.wlock(), repo.lock(), repo.transaction('branches'):
722 # abort in case of uncommitted merge or dirty wdir
722 # abort in case of uncommitted merge or dirty wdir
723 bailifchanged(repo)
723 bailifchanged(repo)
724 revs = scmutil.revrange(repo, revs)
724 revs = scmutil.revrange(repo, revs)
725 if not revs:
725 if not revs:
726 raise error.Abort("empty revision set")
726 raise error.Abort("empty revision set")
727 roots = repo.revs('roots(%ld)', revs)
727 roots = repo.revs('roots(%ld)', revs)
728 if len(roots) > 1:
728 if len(roots) > 1:
729 raise error.Abort(_("cannot change branch of non-linear revisions"))
729 raise error.Abort(_("cannot change branch of non-linear revisions"))
730 rewriteutil.precheck(repo, revs, 'change branch of')
730 rewriteutil.precheck(repo, revs, 'change branch of')
731
731
732 root = repo[roots.first()]
732 root = repo[roots.first()]
733 if not root.p1().branch() == label and label in repo.branchmap():
733 if not root.p1().branch() == label and label in repo.branchmap():
734 raise error.Abort(_("a branch of the same name already exists"))
734 raise error.Abort(_("a branch of the same name already exists"))
735
735
736 if repo.revs('merge() and %ld', revs):
736 if repo.revs('merge() and %ld', revs):
737 raise error.Abort(_("cannot change branch of a merge commit"))
737 raise error.Abort(_("cannot change branch of a merge commit"))
738 if repo.revs('obsolete() and %ld', revs):
738 if repo.revs('obsolete() and %ld', revs):
739 raise error.Abort(_("cannot change branch of a obsolete changeset"))
739 raise error.Abort(_("cannot change branch of a obsolete changeset"))
740
740
741 # make sure only topological heads
741 # make sure only topological heads
742 if repo.revs('heads(%ld) - head()', revs):
742 if repo.revs('heads(%ld) - head()', revs):
743 raise error.Abort(_("cannot change branch in middle of a stack"))
743 raise error.Abort(_("cannot change branch in middle of a stack"))
744
744
745 replacements = {}
745 replacements = {}
746 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
746 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
747 # mercurial.subrepo -> mercurial.cmdutil
747 # mercurial.subrepo -> mercurial.cmdutil
748 from . import context
748 from . import context
749 for rev in revs:
749 for rev in revs:
750 ctx = repo[rev]
750 ctx = repo[rev]
751 oldbranch = ctx.branch()
751 oldbranch = ctx.branch()
752 # check if ctx has same branch
752 # check if ctx has same branch
753 if oldbranch == label:
753 if oldbranch == label:
754 continue
754 continue
755
755
756 def filectxfn(repo, newctx, path):
756 def filectxfn(repo, newctx, path):
757 try:
757 try:
758 return ctx[path]
758 return ctx[path]
759 except error.ManifestLookupError:
759 except error.ManifestLookupError:
760 return None
760 return None
761
761
762 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
762 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
763 % (hex(ctx.node()), oldbranch, label))
763 % (hex(ctx.node()), oldbranch, label))
764 extra = ctx.extra()
764 extra = ctx.extra()
765 extra['branch_change'] = hex(ctx.node())
765 extra['branch_change'] = hex(ctx.node())
766 # While changing branch of set of linear commits, make sure that
766 # While changing branch of set of linear commits, make sure that
767 # we base our commits on new parent rather than old parent which
767 # we base our commits on new parent rather than old parent which
768 # was obsoleted while changing the branch
768 # was obsoleted while changing the branch
769 p1 = ctx.p1().node()
769 p1 = ctx.p1().node()
770 p2 = ctx.p2().node()
770 p2 = ctx.p2().node()
771 if p1 in replacements:
771 if p1 in replacements:
772 p1 = replacements[p1][0]
772 p1 = replacements[p1][0]
773 if p2 in replacements:
773 if p2 in replacements:
774 p2 = replacements[p2][0]
774 p2 = replacements[p2][0]
775
775
776 mc = context.memctx(repo, (p1, p2),
776 mc = context.memctx(repo, (p1, p2),
777 ctx.description(),
777 ctx.description(),
778 ctx.files(),
778 ctx.files(),
779 filectxfn,
779 filectxfn,
780 user=ctx.user(),
780 user=ctx.user(),
781 date=ctx.date(),
781 date=ctx.date(),
782 extra=extra,
782 extra=extra,
783 branch=label)
783 branch=label)
784
784
785 commitphase = ctx.phase()
785 commitphase = ctx.phase()
786 overrides = {('phases', 'new-commit'): commitphase}
786 overrides = {('phases', 'new-commit'): commitphase}
787 with repo.ui.configoverride(overrides, 'branch-change'):
787 with repo.ui.configoverride(overrides, 'branch-change'):
788 newnode = repo.commitctx(mc)
788 newnode = repo.commitctx(mc)
789
789
790 replacements[ctx.node()] = (newnode,)
790 replacements[ctx.node()] = (newnode,)
791 ui.debug('new node id is %s\n' % hex(newnode))
791 ui.debug('new node id is %s\n' % hex(newnode))
792
792
793 # create obsmarkers and move bookmarks
793 # create obsmarkers and move bookmarks
794 scmutil.cleanupnodes(repo, replacements, 'branch-change')
794 scmutil.cleanupnodes(repo, replacements, 'branch-change')
795
795
796 # move the working copy too
796 # move the working copy too
797 wctx = repo[None]
797 wctx = repo[None]
798 # in-progress merge is a bit too complex for now.
798 # in-progress merge is a bit too complex for now.
799 if len(wctx.parents()) == 1:
799 if len(wctx.parents()) == 1:
800 newid = replacements.get(wctx.p1().node())
800 newid = replacements.get(wctx.p1().node())
801 if newid is not None:
801 if newid is not None:
802 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
802 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
803 # mercurial.cmdutil
803 # mercurial.cmdutil
804 from . import hg
804 from . import hg
805 hg.update(repo, newid[0], quietempty=True)
805 hg.update(repo, newid[0], quietempty=True)
806
806
807 ui.status(_("changed branch on %d changesets\n") % len(replacements))
807 ui.status(_("changed branch on %d changesets\n") % len(replacements))
808
808
809 def findrepo(p):
809 def findrepo(p):
810 while not os.path.isdir(os.path.join(p, ".hg")):
810 while not os.path.isdir(os.path.join(p, ".hg")):
811 oldp, p = p, os.path.dirname(p)
811 oldp, p = p, os.path.dirname(p)
812 if p == oldp:
812 if p == oldp:
813 return None
813 return None
814
814
815 return p
815 return p
816
816
817 def bailifchanged(repo, merge=True, hint=None):
817 def bailifchanged(repo, merge=True, hint=None):
818 """ enforce the precondition that working directory must be clean.
818 """ enforce the precondition that working directory must be clean.
819
819
820 'merge' can be set to false if a pending uncommitted merge should be
820 'merge' can be set to false if a pending uncommitted merge should be
821 ignored (such as when 'update --check' runs).
821 ignored (such as when 'update --check' runs).
822
822
823 'hint' is the usual hint given to Abort exception.
823 'hint' is the usual hint given to Abort exception.
824 """
824 """
825
825
826 if merge and repo.dirstate.p2() != nullid:
826 if merge and repo.dirstate.p2() != nullid:
827 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
827 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
828 modified, added, removed, deleted = repo.status()[:4]
828 modified, added, removed, deleted = repo.status()[:4]
829 if modified or added or removed or deleted:
829 if modified or added or removed or deleted:
830 raise error.Abort(_('uncommitted changes'), hint=hint)
830 raise error.Abort(_('uncommitted changes'), hint=hint)
831 ctx = repo[None]
831 ctx = repo[None]
832 for s in sorted(ctx.substate):
832 for s in sorted(ctx.substate):
833 ctx.sub(s).bailifchanged(hint=hint)
833 ctx.sub(s).bailifchanged(hint=hint)
834
834
835 def logmessage(ui, opts):
835 def logmessage(ui, opts):
836 """ get the log message according to -m and -l option """
836 """ get the log message according to -m and -l option """
837 message = opts.get('message')
837 message = opts.get('message')
838 logfile = opts.get('logfile')
838 logfile = opts.get('logfile')
839
839
840 if message and logfile:
840 if message and logfile:
841 raise error.Abort(_('options --message and --logfile are mutually '
841 raise error.Abort(_('options --message and --logfile are mutually '
842 'exclusive'))
842 'exclusive'))
843 if not message and logfile:
843 if not message and logfile:
844 try:
844 try:
845 if isstdiofilename(logfile):
845 if isstdiofilename(logfile):
846 message = ui.fin.read()
846 message = ui.fin.read()
847 else:
847 else:
848 message = '\n'.join(util.readfile(logfile).splitlines())
848 message = '\n'.join(util.readfile(logfile).splitlines())
849 except IOError as inst:
849 except IOError as inst:
850 raise error.Abort(_("can't read commit message '%s': %s") %
850 raise error.Abort(_("can't read commit message '%s': %s") %
851 (logfile, encoding.strtolocal(inst.strerror)))
851 (logfile, encoding.strtolocal(inst.strerror)))
852 return message
852 return message
853
853
854 def mergeeditform(ctxorbool, baseformname):
854 def mergeeditform(ctxorbool, baseformname):
855 """return appropriate editform name (referencing a committemplate)
855 """return appropriate editform name (referencing a committemplate)
856
856
857 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
857 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
858 merging is committed.
858 merging is committed.
859
859
860 This returns baseformname with '.merge' appended if it is a merge,
860 This returns baseformname with '.merge' appended if it is a merge,
861 otherwise '.normal' is appended.
861 otherwise '.normal' is appended.
862 """
862 """
863 if isinstance(ctxorbool, bool):
863 if isinstance(ctxorbool, bool):
864 if ctxorbool:
864 if ctxorbool:
865 return baseformname + ".merge"
865 return baseformname + ".merge"
866 elif 1 < len(ctxorbool.parents()):
866 elif 1 < len(ctxorbool.parents()):
867 return baseformname + ".merge"
867 return baseformname + ".merge"
868
868
869 return baseformname + ".normal"
869 return baseformname + ".normal"
870
870
871 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
871 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
872 editform='', **opts):
872 editform='', **opts):
873 """get appropriate commit message editor according to '--edit' option
873 """get appropriate commit message editor according to '--edit' option
874
874
875 'finishdesc' is a function to be called with edited commit message
875 'finishdesc' is a function to be called with edited commit message
876 (= 'description' of the new changeset) just after editing, but
876 (= 'description' of the new changeset) just after editing, but
877 before checking empty-ness. It should return actual text to be
877 before checking empty-ness. It should return actual text to be
878 stored into history. This allows to change description before
878 stored into history. This allows to change description before
879 storing.
879 storing.
880
880
881 'extramsg' is a extra message to be shown in the editor instead of
881 'extramsg' is a extra message to be shown in the editor instead of
882 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
882 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
883 is automatically added.
883 is automatically added.
884
884
885 'editform' is a dot-separated list of names, to distinguish
885 'editform' is a dot-separated list of names, to distinguish
886 the purpose of commit text editing.
886 the purpose of commit text editing.
887
887
888 'getcommiteditor' returns 'commitforceeditor' regardless of
888 'getcommiteditor' returns 'commitforceeditor' regardless of
889 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
889 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
890 they are specific for usage in MQ.
890 they are specific for usage in MQ.
891 """
891 """
892 if edit or finishdesc or extramsg:
892 if edit or finishdesc or extramsg:
893 return lambda r, c, s: commitforceeditor(r, c, s,
893 return lambda r, c, s: commitforceeditor(r, c, s,
894 finishdesc=finishdesc,
894 finishdesc=finishdesc,
895 extramsg=extramsg,
895 extramsg=extramsg,
896 editform=editform)
896 editform=editform)
897 elif editform:
897 elif editform:
898 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
898 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
899 else:
899 else:
900 return commiteditor
900 return commiteditor
901
901
902 def rendertemplate(ctx, tmpl, props=None):
902 def rendertemplate(ctx, tmpl, props=None):
903 """Expand a literal template 'tmpl' byte-string against one changeset
903 """Expand a literal template 'tmpl' byte-string against one changeset
904
904
905 Each props item must be a stringify-able value or a callable returning
905 Each props item must be a stringify-able value or a callable returning
906 such value, i.e. no bare list nor dict should be passed.
906 such value, i.e. no bare list nor dict should be passed.
907 """
907 """
908 repo = ctx.repo()
908 repo = ctx.repo()
909 tres = formatter.templateresources(repo.ui, repo)
909 tres = formatter.templateresources(repo.ui, repo)
910 t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords,
910 t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords,
911 resources=tres)
911 resources=tres)
912 mapping = {'ctx': ctx}
912 mapping = {'ctx': ctx}
913 if props:
913 if props:
914 mapping.update(props)
914 mapping.update(props)
915 return t.renderdefault(mapping)
915 return t.renderdefault(mapping)
916
916
917 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
917 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
918 r"""Convert old-style filename format string to template string
918 r"""Convert old-style filename format string to template string
919
919
920 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
920 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
921 'foo-{reporoot|basename}-{seqno}.patch'
921 'foo-{reporoot|basename}-{seqno}.patch'
922 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
922 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
923 '{rev}{tags % "{tag}"}{node}'
923 '{rev}{tags % "{tag}"}{node}'
924
924
925 '\' in outermost strings has to be escaped because it is a directory
925 '\' in outermost strings has to be escaped because it is a directory
926 separator on Windows:
926 separator on Windows:
927
927
928 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
928 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
929 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
929 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
930 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
930 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
931 '\\\\\\\\foo\\\\bar.patch'
931 '\\\\\\\\foo\\\\bar.patch'
932 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
932 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
933 '\\\\{tags % "{tag}"}'
933 '\\\\{tags % "{tag}"}'
934
934
935 but inner strings follow the template rules (i.e. '\' is taken as an
935 but inner strings follow the template rules (i.e. '\' is taken as an
936 escape character):
936 escape character):
937
937
938 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
938 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
939 '{"c:\\tmp"}'
939 '{"c:\\tmp"}'
940 """
940 """
941 expander = {
941 expander = {
942 b'H': b'{node}',
942 b'H': b'{node}',
943 b'R': b'{rev}',
943 b'R': b'{rev}',
944 b'h': b'{node|short}',
944 b'h': b'{node|short}',
945 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
945 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
946 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
946 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
947 b'%': b'%',
947 b'%': b'%',
948 b'b': b'{reporoot|basename}',
948 b'b': b'{reporoot|basename}',
949 }
949 }
950 if total is not None:
950 if total is not None:
951 expander[b'N'] = b'{total}'
951 expander[b'N'] = b'{total}'
952 if seqno is not None:
952 if seqno is not None:
953 expander[b'n'] = b'{seqno}'
953 expander[b'n'] = b'{seqno}'
954 if total is not None and seqno is not None:
954 if total is not None and seqno is not None:
955 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
955 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
956 if pathname is not None:
956 if pathname is not None:
957 expander[b's'] = b'{pathname|basename}'
957 expander[b's'] = b'{pathname|basename}'
958 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
958 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
959 expander[b'p'] = b'{pathname}'
959 expander[b'p'] = b'{pathname}'
960
960
961 newname = []
961 newname = []
962 for typ, start, end in templater.scantemplate(pat, raw=True):
962 for typ, start, end in templater.scantemplate(pat, raw=True):
963 if typ != b'string':
963 if typ != b'string':
964 newname.append(pat[start:end])
964 newname.append(pat[start:end])
965 continue
965 continue
966 i = start
966 i = start
967 while i < end:
967 while i < end:
968 n = pat.find(b'%', i, end)
968 n = pat.find(b'%', i, end)
969 if n < 0:
969 if n < 0:
970 newname.append(stringutil.escapestr(pat[i:end]))
970 newname.append(stringutil.escapestr(pat[i:end]))
971 break
971 break
972 newname.append(stringutil.escapestr(pat[i:n]))
972 newname.append(stringutil.escapestr(pat[i:n]))
973 if n + 2 > end:
973 if n + 2 > end:
974 raise error.Abort(_("incomplete format spec in output "
974 raise error.Abort(_("incomplete format spec in output "
975 "filename"))
975 "filename"))
976 c = pat[n + 1:n + 2]
976 c = pat[n + 1:n + 2]
977 i = n + 2
977 i = n + 2
978 try:
978 try:
979 newname.append(expander[c])
979 newname.append(expander[c])
980 except KeyError:
980 except KeyError:
981 raise error.Abort(_("invalid format spec '%%%s' in output "
981 raise error.Abort(_("invalid format spec '%%%s' in output "
982 "filename") % c)
982 "filename") % c)
983 return ''.join(newname)
983 return ''.join(newname)
984
984
985 def makefilename(ctx, pat, **props):
985 def makefilename(ctx, pat, **props):
986 if not pat:
986 if not pat:
987 return pat
987 return pat
988 tmpl = _buildfntemplate(pat, **props)
988 tmpl = _buildfntemplate(pat, **props)
989 # BUG: alias expansion shouldn't be made against template fragments
989 # BUG: alias expansion shouldn't be made against template fragments
990 # rewritten from %-format strings, but we have no easy way to partially
990 # rewritten from %-format strings, but we have no easy way to partially
991 # disable the expansion.
991 # disable the expansion.
992 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
992 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
993
993
994 def isstdiofilename(pat):
994 def isstdiofilename(pat):
995 """True if the given pat looks like a filename denoting stdin/stdout"""
995 """True if the given pat looks like a filename denoting stdin/stdout"""
996 return not pat or pat == '-'
996 return not pat or pat == '-'
997
997
998 class _unclosablefile(object):
998 class _unclosablefile(object):
999 def __init__(self, fp):
999 def __init__(self, fp):
1000 self._fp = fp
1000 self._fp = fp
1001
1001
1002 def close(self):
1002 def close(self):
1003 pass
1003 pass
1004
1004
1005 def __iter__(self):
1005 def __iter__(self):
1006 return iter(self._fp)
1006 return iter(self._fp)
1007
1007
1008 def __getattr__(self, attr):
1008 def __getattr__(self, attr):
1009 return getattr(self._fp, attr)
1009 return getattr(self._fp, attr)
1010
1010
1011 def __enter__(self):
1011 def __enter__(self):
1012 return self
1012 return self
1013
1013
1014 def __exit__(self, exc_type, exc_value, exc_tb):
1014 def __exit__(self, exc_type, exc_value, exc_tb):
1015 pass
1015 pass
1016
1016
1017 def makefileobj(ctx, pat, mode='wb', **props):
1017 def makefileobj(ctx, pat, mode='wb', **props):
1018 writable = mode not in ('r', 'rb')
1018 writable = mode not in ('r', 'rb')
1019
1019
1020 if isstdiofilename(pat):
1020 if isstdiofilename(pat):
1021 repo = ctx.repo()
1021 repo = ctx.repo()
1022 if writable:
1022 if writable:
1023 fp = repo.ui.fout
1023 fp = repo.ui.fout
1024 else:
1024 else:
1025 fp = repo.ui.fin
1025 fp = repo.ui.fin
1026 return _unclosablefile(fp)
1026 return _unclosablefile(fp)
1027 fn = makefilename(ctx, pat, **props)
1027 fn = makefilename(ctx, pat, **props)
1028 return open(fn, mode)
1028 return open(fn, mode)
1029
1029
1030 def openrevlog(repo, cmd, file_, opts):
1030 def openrevlog(repo, cmd, file_, opts):
1031 """opens the changelog, manifest, a filelog or a given revlog"""
1031 """opens the changelog, manifest, a filelog or a given revlog"""
1032 cl = opts['changelog']
1032 cl = opts['changelog']
1033 mf = opts['manifest']
1033 mf = opts['manifest']
1034 dir = opts['dir']
1034 dir = opts['dir']
1035 msg = None
1035 msg = None
1036 if cl and mf:
1036 if cl and mf:
1037 msg = _('cannot specify --changelog and --manifest at the same time')
1037 msg = _('cannot specify --changelog and --manifest at the same time')
1038 elif cl and dir:
1038 elif cl and dir:
1039 msg = _('cannot specify --changelog and --dir at the same time')
1039 msg = _('cannot specify --changelog and --dir at the same time')
1040 elif cl or mf or dir:
1040 elif cl or mf or dir:
1041 if file_:
1041 if file_:
1042 msg = _('cannot specify filename with --changelog or --manifest')
1042 msg = _('cannot specify filename with --changelog or --manifest')
1043 elif not repo:
1043 elif not repo:
1044 msg = _('cannot specify --changelog or --manifest or --dir '
1044 msg = _('cannot specify --changelog or --manifest or --dir '
1045 'without a repository')
1045 'without a repository')
1046 if msg:
1046 if msg:
1047 raise error.Abort(msg)
1047 raise error.Abort(msg)
1048
1048
1049 r = None
1049 r = None
1050 if repo:
1050 if repo:
1051 if cl:
1051 if cl:
1052 r = repo.unfiltered().changelog
1052 r = repo.unfiltered().changelog
1053 elif dir:
1053 elif dir:
1054 if 'treemanifest' not in repo.requirements:
1054 if 'treemanifest' not in repo.requirements:
1055 raise error.Abort(_("--dir can only be used on repos with "
1055 raise error.Abort(_("--dir can only be used on repos with "
1056 "treemanifest enabled"))
1056 "treemanifest enabled"))
1057 if not dir.endswith('/'):
1057 if not dir.endswith('/'):
1058 dir = dir + '/'
1058 dir = dir + '/'
1059 dirlog = repo.manifestlog._revlog.dirlog(dir)
1059 dirlog = repo.manifestlog._revlog.dirlog(dir)
1060 if len(dirlog):
1060 if len(dirlog):
1061 r = dirlog
1061 r = dirlog
1062 elif mf:
1062 elif mf:
1063 r = repo.manifestlog._revlog
1063 r = repo.manifestlog._revlog
1064 elif file_:
1064 elif file_:
1065 filelog = repo.file(file_)
1065 filelog = repo.file(file_)
1066 if len(filelog):
1066 if len(filelog):
1067 r = filelog
1067 r = filelog
1068 if not r:
1068 if not r:
1069 if not file_:
1069 if not file_:
1070 raise error.CommandError(cmd, _('invalid arguments'))
1070 raise error.CommandError(cmd, _('invalid arguments'))
1071 if not os.path.isfile(file_):
1071 if not os.path.isfile(file_):
1072 raise error.Abort(_("revlog '%s' not found") % file_)
1072 raise error.Abort(_("revlog '%s' not found") % file_)
1073 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
1073 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
1074 file_[:-2] + ".i")
1074 file_[:-2] + ".i")
1075 return r
1075 return r
1076
1076
1077 def copy(ui, repo, pats, opts, rename=False):
1077 def copy(ui, repo, pats, opts, rename=False):
1078 # called with the repo lock held
1078 # called with the repo lock held
1079 #
1079 #
1080 # hgsep => pathname that uses "/" to separate directories
1080 # hgsep => pathname that uses "/" to separate directories
1081 # ossep => pathname that uses os.sep to separate directories
1081 # ossep => pathname that uses os.sep to separate directories
1082 cwd = repo.getcwd()
1082 cwd = repo.getcwd()
1083 targets = {}
1083 targets = {}
1084 after = opts.get("after")
1084 after = opts.get("after")
1085 dryrun = opts.get("dry_run")
1085 dryrun = opts.get("dry_run")
1086 wctx = repo[None]
1086 wctx = repo[None]
1087
1087
1088 def walkpat(pat):
1088 def walkpat(pat):
1089 srcs = []
1089 srcs = []
1090 if after:
1090 if after:
1091 badstates = '?'
1091 badstates = '?'
1092 else:
1092 else:
1093 badstates = '?r'
1093 badstates = '?r'
1094 m = scmutil.match(wctx, [pat], opts, globbed=True)
1094 m = scmutil.match(wctx, [pat], opts, globbed=True)
1095 for abs in wctx.walk(m):
1095 for abs in wctx.walk(m):
1096 state = repo.dirstate[abs]
1096 state = repo.dirstate[abs]
1097 rel = m.rel(abs)
1097 rel = m.rel(abs)
1098 exact = m.exact(abs)
1098 exact = m.exact(abs)
1099 if state in badstates:
1099 if state in badstates:
1100 if exact and state == '?':
1100 if exact and state == '?':
1101 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1101 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1102 if exact and state == 'r':
1102 if exact and state == 'r':
1103 ui.warn(_('%s: not copying - file has been marked for'
1103 ui.warn(_('%s: not copying - file has been marked for'
1104 ' remove\n') % rel)
1104 ' remove\n') % rel)
1105 continue
1105 continue
1106 # abs: hgsep
1106 # abs: hgsep
1107 # rel: ossep
1107 # rel: ossep
1108 srcs.append((abs, rel, exact))
1108 srcs.append((abs, rel, exact))
1109 return srcs
1109 return srcs
1110
1110
1111 # abssrc: hgsep
1111 # abssrc: hgsep
1112 # relsrc: ossep
1112 # relsrc: ossep
1113 # otarget: ossep
1113 # otarget: ossep
1114 def copyfile(abssrc, relsrc, otarget, exact):
1114 def copyfile(abssrc, relsrc, otarget, exact):
1115 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1115 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1116 if '/' in abstarget:
1116 if '/' in abstarget:
1117 # We cannot normalize abstarget itself, this would prevent
1117 # We cannot normalize abstarget itself, this would prevent
1118 # case only renames, like a => A.
1118 # case only renames, like a => A.
1119 abspath, absname = abstarget.rsplit('/', 1)
1119 abspath, absname = abstarget.rsplit('/', 1)
1120 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1120 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1121 reltarget = repo.pathto(abstarget, cwd)
1121 reltarget = repo.pathto(abstarget, cwd)
1122 target = repo.wjoin(abstarget)
1122 target = repo.wjoin(abstarget)
1123 src = repo.wjoin(abssrc)
1123 src = repo.wjoin(abssrc)
1124 state = repo.dirstate[abstarget]
1124 state = repo.dirstate[abstarget]
1125
1125
1126 scmutil.checkportable(ui, abstarget)
1126 scmutil.checkportable(ui, abstarget)
1127
1127
1128 # check for collisions
1128 # check for collisions
1129 prevsrc = targets.get(abstarget)
1129 prevsrc = targets.get(abstarget)
1130 if prevsrc is not None:
1130 if prevsrc is not None:
1131 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1131 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1132 (reltarget, repo.pathto(abssrc, cwd),
1132 (reltarget, repo.pathto(abssrc, cwd),
1133 repo.pathto(prevsrc, cwd)))
1133 repo.pathto(prevsrc, cwd)))
1134 return
1134 return
1135
1135
1136 # check for overwrites
1136 # check for overwrites
1137 exists = os.path.lexists(target)
1137 exists = os.path.lexists(target)
1138 samefile = False
1138 samefile = False
1139 if exists and abssrc != abstarget:
1139 if exists and abssrc != abstarget:
1140 if (repo.dirstate.normalize(abssrc) ==
1140 if (repo.dirstate.normalize(abssrc) ==
1141 repo.dirstate.normalize(abstarget)):
1141 repo.dirstate.normalize(abstarget)):
1142 if not rename:
1142 if not rename:
1143 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1143 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1144 return
1144 return
1145 exists = False
1145 exists = False
1146 samefile = True
1146 samefile = True
1147
1147
1148 if not after and exists or after and state in 'mn':
1148 if not after and exists or after and state in 'mn':
1149 if not opts['force']:
1149 if not opts['force']:
1150 if state in 'mn':
1150 if state in 'mn':
1151 msg = _('%s: not overwriting - file already committed\n')
1151 msg = _('%s: not overwriting - file already committed\n')
1152 if after:
1152 if after:
1153 flags = '--after --force'
1153 flags = '--after --force'
1154 else:
1154 else:
1155 flags = '--force'
1155 flags = '--force'
1156 if rename:
1156 if rename:
1157 hint = _('(hg rename %s to replace the file by '
1157 hint = _('(hg rename %s to replace the file by '
1158 'recording a rename)\n') % flags
1158 'recording a rename)\n') % flags
1159 else:
1159 else:
1160 hint = _('(hg copy %s to replace the file by '
1160 hint = _('(hg copy %s to replace the file by '
1161 'recording a copy)\n') % flags
1161 'recording a copy)\n') % flags
1162 else:
1162 else:
1163 msg = _('%s: not overwriting - file exists\n')
1163 msg = _('%s: not overwriting - file exists\n')
1164 if rename:
1164 if rename:
1165 hint = _('(hg rename --after to record the rename)\n')
1165 hint = _('(hg rename --after to record the rename)\n')
1166 else:
1166 else:
1167 hint = _('(hg copy --after to record the copy)\n')
1167 hint = _('(hg copy --after to record the copy)\n')
1168 ui.warn(msg % reltarget)
1168 ui.warn(msg % reltarget)
1169 ui.warn(hint)
1169 ui.warn(hint)
1170 return
1170 return
1171
1171
1172 if after:
1172 if after:
1173 if not exists:
1173 if not exists:
1174 if rename:
1174 if rename:
1175 ui.warn(_('%s: not recording move - %s does not exist\n') %
1175 ui.warn(_('%s: not recording move - %s does not exist\n') %
1176 (relsrc, reltarget))
1176 (relsrc, reltarget))
1177 else:
1177 else:
1178 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1178 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1179 (relsrc, reltarget))
1179 (relsrc, reltarget))
1180 return
1180 return
1181 elif not dryrun:
1181 elif not dryrun:
1182 try:
1182 try:
1183 if exists:
1183 if exists:
1184 os.unlink(target)
1184 os.unlink(target)
1185 targetdir = os.path.dirname(target) or '.'
1185 targetdir = os.path.dirname(target) or '.'
1186 if not os.path.isdir(targetdir):
1186 if not os.path.isdir(targetdir):
1187 os.makedirs(targetdir)
1187 os.makedirs(targetdir)
1188 if samefile:
1188 if samefile:
1189 tmp = target + "~hgrename"
1189 tmp = target + "~hgrename"
1190 os.rename(src, tmp)
1190 os.rename(src, tmp)
1191 os.rename(tmp, target)
1191 os.rename(tmp, target)
1192 else:
1192 else:
1193 # Preserve stat info on renames, not on copies; this matches
1193 # Preserve stat info on renames, not on copies; this matches
1194 # Linux CLI behavior.
1194 # Linux CLI behavior.
1195 util.copyfile(src, target, copystat=rename)
1195 util.copyfile(src, target, copystat=rename)
1196 srcexists = True
1196 srcexists = True
1197 except IOError as inst:
1197 except IOError as inst:
1198 if inst.errno == errno.ENOENT:
1198 if inst.errno == errno.ENOENT:
1199 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1199 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1200 srcexists = False
1200 srcexists = False
1201 else:
1201 else:
1202 ui.warn(_('%s: cannot copy - %s\n') %
1202 ui.warn(_('%s: cannot copy - %s\n') %
1203 (relsrc, encoding.strtolocal(inst.strerror)))
1203 (relsrc, encoding.strtolocal(inst.strerror)))
1204 return True # report a failure
1204 return True # report a failure
1205
1205
1206 if ui.verbose or not exact:
1206 if ui.verbose or not exact:
1207 if rename:
1207 if rename:
1208 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1208 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1209 else:
1209 else:
1210 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1210 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1211
1211
1212 targets[abstarget] = abssrc
1212 targets[abstarget] = abssrc
1213
1213
1214 # fix up dirstate
1214 # fix up dirstate
1215 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1215 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1216 dryrun=dryrun, cwd=cwd)
1216 dryrun=dryrun, cwd=cwd)
1217 if rename and not dryrun:
1217 if rename and not dryrun:
1218 if not after and srcexists and not samefile:
1218 if not after and srcexists and not samefile:
1219 repo.wvfs.unlinkpath(abssrc)
1219 repo.wvfs.unlinkpath(abssrc)
1220 wctx.forget([abssrc])
1220 wctx.forget([abssrc])
1221
1221
1222 # pat: ossep
1222 # pat: ossep
1223 # dest ossep
1223 # dest ossep
1224 # srcs: list of (hgsep, hgsep, ossep, bool)
1224 # srcs: list of (hgsep, hgsep, ossep, bool)
1225 # return: function that takes hgsep and returns ossep
1225 # return: function that takes hgsep and returns ossep
1226 def targetpathfn(pat, dest, srcs):
1226 def targetpathfn(pat, dest, srcs):
1227 if os.path.isdir(pat):
1227 if os.path.isdir(pat):
1228 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1228 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1229 abspfx = util.localpath(abspfx)
1229 abspfx = util.localpath(abspfx)
1230 if destdirexists:
1230 if destdirexists:
1231 striplen = len(os.path.split(abspfx)[0])
1231 striplen = len(os.path.split(abspfx)[0])
1232 else:
1232 else:
1233 striplen = len(abspfx)
1233 striplen = len(abspfx)
1234 if striplen:
1234 if striplen:
1235 striplen += len(pycompat.ossep)
1235 striplen += len(pycompat.ossep)
1236 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1236 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1237 elif destdirexists:
1237 elif destdirexists:
1238 res = lambda p: os.path.join(dest,
1238 res = lambda p: os.path.join(dest,
1239 os.path.basename(util.localpath(p)))
1239 os.path.basename(util.localpath(p)))
1240 else:
1240 else:
1241 res = lambda p: dest
1241 res = lambda p: dest
1242 return res
1242 return res
1243
1243
1244 # pat: ossep
1244 # pat: ossep
1245 # dest ossep
1245 # dest ossep
1246 # srcs: list of (hgsep, hgsep, ossep, bool)
1246 # srcs: list of (hgsep, hgsep, ossep, bool)
1247 # return: function that takes hgsep and returns ossep
1247 # return: function that takes hgsep and returns ossep
1248 def targetpathafterfn(pat, dest, srcs):
1248 def targetpathafterfn(pat, dest, srcs):
1249 if matchmod.patkind(pat):
1249 if matchmod.patkind(pat):
1250 # a mercurial pattern
1250 # a mercurial pattern
1251 res = lambda p: os.path.join(dest,
1251 res = lambda p: os.path.join(dest,
1252 os.path.basename(util.localpath(p)))
1252 os.path.basename(util.localpath(p)))
1253 else:
1253 else:
1254 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1254 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1255 if len(abspfx) < len(srcs[0][0]):
1255 if len(abspfx) < len(srcs[0][0]):
1256 # A directory. Either the target path contains the last
1256 # A directory. Either the target path contains the last
1257 # component of the source path or it does not.
1257 # component of the source path or it does not.
1258 def evalpath(striplen):
1258 def evalpath(striplen):
1259 score = 0
1259 score = 0
1260 for s in srcs:
1260 for s in srcs:
1261 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1261 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1262 if os.path.lexists(t):
1262 if os.path.lexists(t):
1263 score += 1
1263 score += 1
1264 return score
1264 return score
1265
1265
1266 abspfx = util.localpath(abspfx)
1266 abspfx = util.localpath(abspfx)
1267 striplen = len(abspfx)
1267 striplen = len(abspfx)
1268 if striplen:
1268 if striplen:
1269 striplen += len(pycompat.ossep)
1269 striplen += len(pycompat.ossep)
1270 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1270 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1271 score = evalpath(striplen)
1271 score = evalpath(striplen)
1272 striplen1 = len(os.path.split(abspfx)[0])
1272 striplen1 = len(os.path.split(abspfx)[0])
1273 if striplen1:
1273 if striplen1:
1274 striplen1 += len(pycompat.ossep)
1274 striplen1 += len(pycompat.ossep)
1275 if evalpath(striplen1) > score:
1275 if evalpath(striplen1) > score:
1276 striplen = striplen1
1276 striplen = striplen1
1277 res = lambda p: os.path.join(dest,
1277 res = lambda p: os.path.join(dest,
1278 util.localpath(p)[striplen:])
1278 util.localpath(p)[striplen:])
1279 else:
1279 else:
1280 # a file
1280 # a file
1281 if destdirexists:
1281 if destdirexists:
1282 res = lambda p: os.path.join(dest,
1282 res = lambda p: os.path.join(dest,
1283 os.path.basename(util.localpath(p)))
1283 os.path.basename(util.localpath(p)))
1284 else:
1284 else:
1285 res = lambda p: dest
1285 res = lambda p: dest
1286 return res
1286 return res
1287
1287
1288 pats = scmutil.expandpats(pats)
1288 pats = scmutil.expandpats(pats)
1289 if not pats:
1289 if not pats:
1290 raise error.Abort(_('no source or destination specified'))
1290 raise error.Abort(_('no source or destination specified'))
1291 if len(pats) == 1:
1291 if len(pats) == 1:
1292 raise error.Abort(_('no destination specified'))
1292 raise error.Abort(_('no destination specified'))
1293 dest = pats.pop()
1293 dest = pats.pop()
1294 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1294 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1295 if not destdirexists:
1295 if not destdirexists:
1296 if len(pats) > 1 or matchmod.patkind(pats[0]):
1296 if len(pats) > 1 or matchmod.patkind(pats[0]):
1297 raise error.Abort(_('with multiple sources, destination must be an '
1297 raise error.Abort(_('with multiple sources, destination must be an '
1298 'existing directory'))
1298 'existing directory'))
1299 if util.endswithsep(dest):
1299 if util.endswithsep(dest):
1300 raise error.Abort(_('destination %s is not a directory') % dest)
1300 raise error.Abort(_('destination %s is not a directory') % dest)
1301
1301
1302 tfn = targetpathfn
1302 tfn = targetpathfn
1303 if after:
1303 if after:
1304 tfn = targetpathafterfn
1304 tfn = targetpathafterfn
1305 copylist = []
1305 copylist = []
1306 for pat in pats:
1306 for pat in pats:
1307 srcs = walkpat(pat)
1307 srcs = walkpat(pat)
1308 if not srcs:
1308 if not srcs:
1309 continue
1309 continue
1310 copylist.append((tfn(pat, dest, srcs), srcs))
1310 copylist.append((tfn(pat, dest, srcs), srcs))
1311 if not copylist:
1311 if not copylist:
1312 raise error.Abort(_('no files to copy'))
1312 raise error.Abort(_('no files to copy'))
1313
1313
1314 errors = 0
1314 errors = 0
1315 for targetpath, srcs in copylist:
1315 for targetpath, srcs in copylist:
1316 for abssrc, relsrc, exact in srcs:
1316 for abssrc, relsrc, exact in srcs:
1317 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1317 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1318 errors += 1
1318 errors += 1
1319
1319
1320 if errors:
1320 if errors:
1321 ui.warn(_('(consider using --after)\n'))
1321 ui.warn(_('(consider using --after)\n'))
1322
1322
1323 return errors != 0
1323 return errors != 0
1324
1324
1325 ## facility to let extension process additional data into an import patch
1325 ## facility to let extension process additional data into an import patch
1326 # list of identifier to be executed in order
1326 # list of identifier to be executed in order
1327 extrapreimport = [] # run before commit
1327 extrapreimport = [] # run before commit
1328 extrapostimport = [] # run after commit
1328 extrapostimport = [] # run after commit
1329 # mapping from identifier to actual import function
1329 # mapping from identifier to actual import function
1330 #
1330 #
1331 # 'preimport' are run before the commit is made and are provided the following
1331 # 'preimport' are run before the commit is made and are provided the following
1332 # arguments:
1332 # arguments:
1333 # - repo: the localrepository instance,
1333 # - repo: the localrepository instance,
1334 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1334 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1335 # - extra: the future extra dictionary of the changeset, please mutate it,
1335 # - extra: the future extra dictionary of the changeset, please mutate it,
1336 # - opts: the import options.
1336 # - opts: the import options.
1337 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1337 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1338 # mutation of in memory commit and more. Feel free to rework the code to get
1338 # mutation of in memory commit and more. Feel free to rework the code to get
1339 # there.
1339 # there.
1340 extrapreimportmap = {}
1340 extrapreimportmap = {}
1341 # 'postimport' are run after the commit is made and are provided the following
1341 # 'postimport' are run after the commit is made and are provided the following
1342 # argument:
1342 # argument:
1343 # - ctx: the changectx created by import.
1343 # - ctx: the changectx created by import.
1344 extrapostimportmap = {}
1344 extrapostimportmap = {}
1345
1345
1346 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1346 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1347 """Utility function used by commands.import to import a single patch
1347 """Utility function used by commands.import to import a single patch
1348
1348
1349 This function is explicitly defined here to help the evolve extension to
1349 This function is explicitly defined here to help the evolve extension to
1350 wrap this part of the import logic.
1350 wrap this part of the import logic.
1351
1351
1352 The API is currently a bit ugly because it a simple code translation from
1352 The API is currently a bit ugly because it a simple code translation from
1353 the import command. Feel free to make it better.
1353 the import command. Feel free to make it better.
1354
1354
1355 :patchdata: a dictionary containing parsed patch data (such as from
1355 :patchdata: a dictionary containing parsed patch data (such as from
1356 ``patch.extract()``)
1356 ``patch.extract()``)
1357 :parents: nodes that will be parent of the created commit
1357 :parents: nodes that will be parent of the created commit
1358 :opts: the full dict of option passed to the import command
1358 :opts: the full dict of option passed to the import command
1359 :msgs: list to save commit message to.
1359 :msgs: list to save commit message to.
1360 (used in case we need to save it when failing)
1360 (used in case we need to save it when failing)
1361 :updatefunc: a function that update a repo to a given node
1361 :updatefunc: a function that update a repo to a given node
1362 updatefunc(<repo>, <node>)
1362 updatefunc(<repo>, <node>)
1363 """
1363 """
1364 # avoid cycle context -> subrepo -> cmdutil
1364 # avoid cycle context -> subrepo -> cmdutil
1365 from . import context
1365 from . import context
1366
1366
1367 tmpname = patchdata.get('filename')
1367 tmpname = patchdata.get('filename')
1368 message = patchdata.get('message')
1368 message = patchdata.get('message')
1369 user = opts.get('user') or patchdata.get('user')
1369 user = opts.get('user') or patchdata.get('user')
1370 date = opts.get('date') or patchdata.get('date')
1370 date = opts.get('date') or patchdata.get('date')
1371 branch = patchdata.get('branch')
1371 branch = patchdata.get('branch')
1372 nodeid = patchdata.get('nodeid')
1372 nodeid = patchdata.get('nodeid')
1373 p1 = patchdata.get('p1')
1373 p1 = patchdata.get('p1')
1374 p2 = patchdata.get('p2')
1374 p2 = patchdata.get('p2')
1375
1375
1376 nocommit = opts.get('no_commit')
1376 nocommit = opts.get('no_commit')
1377 importbranch = opts.get('import_branch')
1377 importbranch = opts.get('import_branch')
1378 update = not opts.get('bypass')
1378 update = not opts.get('bypass')
1379 strip = opts["strip"]
1379 strip = opts["strip"]
1380 prefix = opts["prefix"]
1380 prefix = opts["prefix"]
1381 sim = float(opts.get('similarity') or 0)
1381 sim = float(opts.get('similarity') or 0)
1382
1382 if not tmpname:
1383 if not tmpname:
1383 return (None, None, False)
1384 return None, None, False
1384
1385
1385 rejects = False
1386 rejects = False
1386
1387
1387 try:
1388 cmdline_message = logmessage(ui, opts)
1388 cmdline_message = logmessage(ui, opts)
1389 if cmdline_message:
1389 if cmdline_message:
1390 # pickup the cmdline msg
1390 # pickup the cmdline msg
1391 message = cmdline_message
1391 message = cmdline_message
1392 elif message:
1392 elif message:
1393 # pickup the patch msg
1393 # pickup the patch msg
1394 message = message.strip()
1394 message = message.strip()
1395 else:
1395 else:
1396 # launch the editor
1396 # launch the editor
1397 message = None
1397 message = None
1398 ui.debug('message:\n%s\n' % (message or ''))
1398 ui.debug('message:\n%s\n' % (message or ''))
1399
1399
1400 if len(parents) == 1:
1400 if len(parents) == 1:
1401 parents.append(repo[nullid])
1401 parents.append(repo[nullid])
1402 if opts.get('exact'):
1402 if opts.get('exact'):
1403 if not nodeid or not p1:
1403 if not nodeid or not p1:
1404 raise error.Abort(_('not a Mercurial patch'))
1404 raise error.Abort(_('not a Mercurial patch'))
1405 p1 = repo[p1]
1406 p2 = repo[p2 or nullid]
1407 elif p2:
1408 try:
1405 p1 = repo[p1]
1409 p1 = repo[p1]
1406 p2 = repo[p2 or nullid]
1410 p2 = repo[p2]
1407 elif p2:
1411 # Without any options, consider p2 only if the
1408 try:
1412 # patch is being applied on top of the recorded
1409 p1 = repo[p1]
1413 # first parent.
1410 p2 = repo[p2]
1414 if p1 != parents[0]:
1411 # Without any options, consider p2 only if the
1415 p1 = parents[0]
1412 # patch is being applied on top of the recorded
1416 p2 = repo[nullid]
1413 # first parent.
1417 except error.RepoError:
1414 if p1 != parents[0]:
1418 p1, p2 = parents
1415 p1 = parents[0]
1419 if p2.node() == nullid:
1416 p2 = repo[nullid]
1420 ui.warn(_("warning: import the patch as a normal revision\n"
1417 except error.RepoError:
1421 "(use --exact to import the patch as a merge)\n"))
1418 p1, p2 = parents
1422 else:
1419 if p2.node() == nullid:
1423 p1, p2 = parents
1420 ui.warn(_("warning: import the patch as a normal revision\n"
1424
1421 "(use --exact to import the patch as a merge)\n"))
1425 n = None
1426 if update:
1427 if p1 != parents[0]:
1428 updatefunc(repo, p1.node())
1429 if p2 != parents[1]:
1430 repo.setparents(p1.node(), p2.node())
1431
1432 if opts.get('exact') or importbranch:
1433 repo.dirstate.setbranch(branch or 'default')
1434
1435 partial = opts.get('partial', False)
1436 files = set()
1437 try:
1438 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1439 files=files, eolmode=None, similarity=sim / 100.0)
1440 except error.PatchError as e:
1441 if not partial:
1442 raise error.Abort(pycompat.bytestr(e))
1443 if partial:
1444 rejects = True
1445
1446 files = list(files)
1447 if nocommit:
1448 if message:
1449 msgs.append(message)
1422 else:
1450 else:
1423 p1, p2 = parents
1451 if opts.get('exact') or p2:
1424
1452 # If you got here, you either use --force and know what
1425 n = None
1453 # you are doing or used --exact or a merge patch while
1426 if update:
1454 # being updated to its first parent.
1427 if p1 != parents[0]:
1455 m = None
1428 updatefunc(repo, p1.node())
1456 else:
1429 if p2 != parents[1]:
1457 m = scmutil.matchfiles(repo, files or [])
1430 repo.setparents(p1.node(), p2.node())
1458 editform = mergeeditform(repo[None], 'import.normal')
1431
1459 if opts.get('exact'):
1432 if opts.get('exact') or importbranch:
1460 editor = None
1433 repo.dirstate.setbranch(branch or 'default')
1461 else:
1434
1462 editor = getcommiteditor(editform=editform,
1435 partial = opts.get('partial', False)
1463 **pycompat.strkwargs(opts))
1464 extra = {}
1465 for idfunc in extrapreimport:
1466 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1467 overrides = {}
1468 if partial:
1469 overrides[('ui', 'allowemptycommit')] = True
1470 with repo.ui.configoverride(overrides, 'import'):
1471 n = repo.commit(message, user,
1472 date, match=m,
1473 editor=editor, extra=extra)
1474 for idfunc in extrapostimport:
1475 extrapostimportmap[idfunc](repo[n])
1476 else:
1477 if opts.get('exact') or importbranch:
1478 branch = branch or 'default'
1479 else:
1480 branch = p1.branch()
1481 store = patch.filestore()
1482 try:
1436 files = set()
1483 files = set()
1437 try:
1484 try:
1438 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1485 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1439 files=files, eolmode=None, similarity=sim / 100.0)
1486 files, eolmode=None)
1440 except error.PatchError as e:
1487 except error.PatchError as e:
1441 if not partial:
1488 raise error.Abort(stringutil.forcebytestr(e))
1442 raise error.Abort(pycompat.bytestr(e))
1489 if opts.get('exact'):
1443 if partial:
1490 editor = None
1444 rejects = True
1445
1446 files = list(files)
1447 if nocommit:
1448 if message:
1449 msgs.append(message)
1450 else:
1491 else:
1451 if opts.get('exact') or p2:
1492 editor = getcommiteditor(editform='import.bypass')
1452 # If you got here, you either use --force and know what
1493 memctx = context.memctx(repo, (p1.node(), p2.node()),
1453 # you are doing or used --exact or a merge patch while
1494 message,
1454 # being updated to its first parent.
1495 files=files,
1455 m = None
1496 filectxfn=store,
1456 else:
1497 user=user,
1457 m = scmutil.matchfiles(repo, files or [])
1498 date=date,
1458 editform = mergeeditform(repo[None], 'import.normal')
1499 branch=branch,
1459 if opts.get('exact'):
1500 editor=editor)
1460 editor = None
1501 n = memctx.commit()
1461 else:
1502 finally:
1462 editor = getcommiteditor(editform=editform,
1503 store.close()
1463 **pycompat.strkwargs(opts))
1504 if opts.get('exact') and nocommit:
1464 extra = {}
1505 # --exact with --no-commit is still useful in that it does merge
1465 for idfunc in extrapreimport:
1506 # and branch bits
1466 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1507 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1467 overrides = {}
1508 elif opts.get('exact') and hex(n) != nodeid:
1468 if partial:
1509 raise error.Abort(_('patch is damaged or loses information'))
1469 overrides[('ui', 'allowemptycommit')] = True
1510 msg = _('applied to working directory')
1470 with repo.ui.configoverride(overrides, 'import'):
1511 if n:
1471 n = repo.commit(message, user,
1512 # i18n: refers to a short changeset id
1472 date, match=m,
1513 msg = _('created %s') % short(n)
1473 editor=editor, extra=extra)
1514 return msg, n, rejects
1474 for idfunc in extrapostimport:
1475 extrapostimportmap[idfunc](repo[n])
1476 else:
1477 if opts.get('exact') or importbranch:
1478 branch = branch or 'default'
1479 else:
1480 branch = p1.branch()
1481 store = patch.filestore()
1482 try:
1483 files = set()
1484 try:
1485 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1486 files, eolmode=None)
1487 except error.PatchError as e:
1488 raise error.Abort(stringutil.forcebytestr(e))
1489 if opts.get('exact'):
1490 editor = None
1491 else:
1492 editor = getcommiteditor(editform='import.bypass')
1493 memctx = context.memctx(repo, (p1.node(), p2.node()),
1494 message,
1495 files=files,
1496 filectxfn=store,
1497 user=user,
1498 date=date,
1499 branch=branch,
1500 editor=editor)
1501 n = memctx.commit()
1502 finally:
1503 store.close()
1504 if opts.get('exact') and nocommit:
1505 # --exact with --no-commit is still useful in that it does merge
1506 # and branch bits
1507 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1508 elif opts.get('exact') and hex(n) != nodeid:
1509 raise error.Abort(_('patch is damaged or loses information'))
1510 msg = _('applied to working directory')
1511 if n:
1512 # i18n: refers to a short changeset id
1513 msg = _('created %s') % short(n)
1514 return (msg, n, rejects)
1515 finally:
1516 os.unlink(tmpname)
1517
1515
1518 # facility to let extensions include additional data in an exported patch
1516 # facility to let extensions include additional data in an exported patch
1519 # list of identifiers to be executed in order
1517 # list of identifiers to be executed in order
1520 extraexport = []
1518 extraexport = []
1521 # mapping from identifier to actual export function
1519 # mapping from identifier to actual export function
1522 # function as to return a string to be added to the header or None
1520 # function as to return a string to be added to the header or None
1523 # it is given two arguments (sequencenumber, changectx)
1521 # it is given two arguments (sequencenumber, changectx)
1524 extraexportmap = {}
1522 extraexportmap = {}
1525
1523
1526 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1524 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1527 node = scmutil.binnode(ctx)
1525 node = scmutil.binnode(ctx)
1528 parents = [p.node() for p in ctx.parents() if p]
1526 parents = [p.node() for p in ctx.parents() if p]
1529 branch = ctx.branch()
1527 branch = ctx.branch()
1530 if switch_parent:
1528 if switch_parent:
1531 parents.reverse()
1529 parents.reverse()
1532
1530
1533 if parents:
1531 if parents:
1534 prev = parents[0]
1532 prev = parents[0]
1535 else:
1533 else:
1536 prev = nullid
1534 prev = nullid
1537
1535
1538 fm.context(ctx=ctx)
1536 fm.context(ctx=ctx)
1539 fm.plain('# HG changeset patch\n')
1537 fm.plain('# HG changeset patch\n')
1540 fm.write('user', '# User %s\n', ctx.user())
1538 fm.write('user', '# User %s\n', ctx.user())
1541 fm.plain('# Date %d %d\n' % ctx.date())
1539 fm.plain('# Date %d %d\n' % ctx.date())
1542 fm.write('date', '# %s\n', fm.formatdate(ctx.date()))
1540 fm.write('date', '# %s\n', fm.formatdate(ctx.date()))
1543 fm.condwrite(branch and branch != 'default',
1541 fm.condwrite(branch and branch != 'default',
1544 'branch', '# Branch %s\n', branch)
1542 'branch', '# Branch %s\n', branch)
1545 fm.write('node', '# Node ID %s\n', hex(node))
1543 fm.write('node', '# Node ID %s\n', hex(node))
1546 fm.plain('# Parent %s\n' % hex(prev))
1544 fm.plain('# Parent %s\n' % hex(prev))
1547 if len(parents) > 1:
1545 if len(parents) > 1:
1548 fm.plain('# Parent %s\n' % hex(parents[1]))
1546 fm.plain('# Parent %s\n' % hex(parents[1]))
1549 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name='node'))
1547 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name='node'))
1550
1548
1551 # TODO: redesign extraexportmap function to support formatter
1549 # TODO: redesign extraexportmap function to support formatter
1552 for headerid in extraexport:
1550 for headerid in extraexport:
1553 header = extraexportmap[headerid](seqno, ctx)
1551 header = extraexportmap[headerid](seqno, ctx)
1554 if header is not None:
1552 if header is not None:
1555 fm.plain('# %s\n' % header)
1553 fm.plain('# %s\n' % header)
1556
1554
1557 fm.write('desc', '%s\n', ctx.description().rstrip())
1555 fm.write('desc', '%s\n', ctx.description().rstrip())
1558 fm.plain('\n')
1556 fm.plain('\n')
1559
1557
1560 if fm.isplain():
1558 if fm.isplain():
1561 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1559 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1562 for chunk, label in chunkiter:
1560 for chunk, label in chunkiter:
1563 fm.plain(chunk, label=label)
1561 fm.plain(chunk, label=label)
1564 else:
1562 else:
1565 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1563 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1566 # TODO: make it structured?
1564 # TODO: make it structured?
1567 fm.data(diff=b''.join(chunkiter))
1565 fm.data(diff=b''.join(chunkiter))
1568
1566
1569 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1567 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1570 """Export changesets to stdout or a single file"""
1568 """Export changesets to stdout or a single file"""
1571 for seqno, rev in enumerate(revs, 1):
1569 for seqno, rev in enumerate(revs, 1):
1572 ctx = repo[rev]
1570 ctx = repo[rev]
1573 if not dest.startswith('<'):
1571 if not dest.startswith('<'):
1574 repo.ui.note("%s\n" % dest)
1572 repo.ui.note("%s\n" % dest)
1575 fm.startitem()
1573 fm.startitem()
1576 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1574 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1577
1575
1578 def _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, diffopts,
1576 def _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, diffopts,
1579 match):
1577 match):
1580 """Export changesets to possibly multiple files"""
1578 """Export changesets to possibly multiple files"""
1581 total = len(revs)
1579 total = len(revs)
1582 revwidth = max(len(str(rev)) for rev in revs)
1580 revwidth = max(len(str(rev)) for rev in revs)
1583 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1581 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1584
1582
1585 for seqno, rev in enumerate(revs, 1):
1583 for seqno, rev in enumerate(revs, 1):
1586 ctx = repo[rev]
1584 ctx = repo[rev]
1587 dest = makefilename(ctx, fntemplate,
1585 dest = makefilename(ctx, fntemplate,
1588 total=total, seqno=seqno, revwidth=revwidth)
1586 total=total, seqno=seqno, revwidth=revwidth)
1589 filemap.setdefault(dest, []).append((seqno, rev))
1587 filemap.setdefault(dest, []).append((seqno, rev))
1590
1588
1591 for dest in filemap:
1589 for dest in filemap:
1592 with formatter.maybereopen(basefm, dest) as fm:
1590 with formatter.maybereopen(basefm, dest) as fm:
1593 repo.ui.note("%s\n" % dest)
1591 repo.ui.note("%s\n" % dest)
1594 for seqno, rev in filemap[dest]:
1592 for seqno, rev in filemap[dest]:
1595 fm.startitem()
1593 fm.startitem()
1596 ctx = repo[rev]
1594 ctx = repo[rev]
1597 _exportsingle(repo, ctx, fm, match, switch_parent, seqno,
1595 _exportsingle(repo, ctx, fm, match, switch_parent, seqno,
1598 diffopts)
1596 diffopts)
1599
1597
1600 def export(repo, revs, basefm, fntemplate='hg-%h.patch', switch_parent=False,
1598 def export(repo, revs, basefm, fntemplate='hg-%h.patch', switch_parent=False,
1601 opts=None, match=None):
1599 opts=None, match=None):
1602 '''export changesets as hg patches
1600 '''export changesets as hg patches
1603
1601
1604 Args:
1602 Args:
1605 repo: The repository from which we're exporting revisions.
1603 repo: The repository from which we're exporting revisions.
1606 revs: A list of revisions to export as revision numbers.
1604 revs: A list of revisions to export as revision numbers.
1607 basefm: A formatter to which patches should be written.
1605 basefm: A formatter to which patches should be written.
1608 fntemplate: An optional string to use for generating patch file names.
1606 fntemplate: An optional string to use for generating patch file names.
1609 switch_parent: If True, show diffs against second parent when not nullid.
1607 switch_parent: If True, show diffs against second parent when not nullid.
1610 Default is false, which always shows diff against p1.
1608 Default is false, which always shows diff against p1.
1611 opts: diff options to use for generating the patch.
1609 opts: diff options to use for generating the patch.
1612 match: If specified, only export changes to files matching this matcher.
1610 match: If specified, only export changes to files matching this matcher.
1613
1611
1614 Returns:
1612 Returns:
1615 Nothing.
1613 Nothing.
1616
1614
1617 Side Effect:
1615 Side Effect:
1618 "HG Changeset Patch" data is emitted to one of the following
1616 "HG Changeset Patch" data is emitted to one of the following
1619 destinations:
1617 destinations:
1620 fntemplate specified: Each rev is written to a unique file named using
1618 fntemplate specified: Each rev is written to a unique file named using
1621 the given template.
1619 the given template.
1622 Otherwise: All revs will be written to basefm.
1620 Otherwise: All revs will be written to basefm.
1623 '''
1621 '''
1624 if not fntemplate:
1622 if not fntemplate:
1625 _exportfile(repo, revs, basefm, '<unnamed>', switch_parent, opts, match)
1623 _exportfile(repo, revs, basefm, '<unnamed>', switch_parent, opts, match)
1626 else:
1624 else:
1627 _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, opts,
1625 _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, opts,
1628 match)
1626 match)
1629
1627
1630 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
1628 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
1631 """Export changesets to the given file stream"""
1629 """Export changesets to the given file stream"""
1632 dest = getattr(fp, 'name', '<unnamed>')
1630 dest = getattr(fp, 'name', '<unnamed>')
1633 with formatter.formatter(repo.ui, fp, 'export', {}) as fm:
1631 with formatter.formatter(repo.ui, fp, 'export', {}) as fm:
1634 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
1632 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
1635
1633
1636 def showmarker(fm, marker, index=None):
1634 def showmarker(fm, marker, index=None):
1637 """utility function to display obsolescence marker in a readable way
1635 """utility function to display obsolescence marker in a readable way
1638
1636
1639 To be used by debug function."""
1637 To be used by debug function."""
1640 if index is not None:
1638 if index is not None:
1641 fm.write('index', '%i ', index)
1639 fm.write('index', '%i ', index)
1642 fm.write('prednode', '%s ', hex(marker.prednode()))
1640 fm.write('prednode', '%s ', hex(marker.prednode()))
1643 succs = marker.succnodes()
1641 succs = marker.succnodes()
1644 fm.condwrite(succs, 'succnodes', '%s ',
1642 fm.condwrite(succs, 'succnodes', '%s ',
1645 fm.formatlist(map(hex, succs), name='node'))
1643 fm.formatlist(map(hex, succs), name='node'))
1646 fm.write('flag', '%X ', marker.flags())
1644 fm.write('flag', '%X ', marker.flags())
1647 parents = marker.parentnodes()
1645 parents = marker.parentnodes()
1648 if parents is not None:
1646 if parents is not None:
1649 fm.write('parentnodes', '{%s} ',
1647 fm.write('parentnodes', '{%s} ',
1650 fm.formatlist(map(hex, parents), name='node', sep=', '))
1648 fm.formatlist(map(hex, parents), name='node', sep=', '))
1651 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1649 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1652 meta = marker.metadata().copy()
1650 meta = marker.metadata().copy()
1653 meta.pop('date', None)
1651 meta.pop('date', None)
1654 smeta = util.rapply(pycompat.maybebytestr, meta)
1652 smeta = util.rapply(pycompat.maybebytestr, meta)
1655 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1653 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1656 fm.plain('\n')
1654 fm.plain('\n')
1657
1655
1658 def finddate(ui, repo, date):
1656 def finddate(ui, repo, date):
1659 """Find the tipmost changeset that matches the given date spec"""
1657 """Find the tipmost changeset that matches the given date spec"""
1660
1658
1661 df = dateutil.matchdate(date)
1659 df = dateutil.matchdate(date)
1662 m = scmutil.matchall(repo)
1660 m = scmutil.matchall(repo)
1663 results = {}
1661 results = {}
1664
1662
1665 def prep(ctx, fns):
1663 def prep(ctx, fns):
1666 d = ctx.date()
1664 d = ctx.date()
1667 if df(d[0]):
1665 if df(d[0]):
1668 results[ctx.rev()] = d
1666 results[ctx.rev()] = d
1669
1667
1670 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1668 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1671 rev = ctx.rev()
1669 rev = ctx.rev()
1672 if rev in results:
1670 if rev in results:
1673 ui.status(_("found revision %s from %s\n") %
1671 ui.status(_("found revision %s from %s\n") %
1674 (rev, dateutil.datestr(results[rev])))
1672 (rev, dateutil.datestr(results[rev])))
1675 return '%d' % rev
1673 return '%d' % rev
1676
1674
1677 raise error.Abort(_("revision matching date not found"))
1675 raise error.Abort(_("revision matching date not found"))
1678
1676
1679 def increasingwindows(windowsize=8, sizelimit=512):
1677 def increasingwindows(windowsize=8, sizelimit=512):
1680 while True:
1678 while True:
1681 yield windowsize
1679 yield windowsize
1682 if windowsize < sizelimit:
1680 if windowsize < sizelimit:
1683 windowsize *= 2
1681 windowsize *= 2
1684
1682
1685 def _walkrevs(repo, opts):
1683 def _walkrevs(repo, opts):
1686 # Default --rev value depends on --follow but --follow behavior
1684 # Default --rev value depends on --follow but --follow behavior
1687 # depends on revisions resolved from --rev...
1685 # depends on revisions resolved from --rev...
1688 follow = opts.get('follow') or opts.get('follow_first')
1686 follow = opts.get('follow') or opts.get('follow_first')
1689 if opts.get('rev'):
1687 if opts.get('rev'):
1690 revs = scmutil.revrange(repo, opts['rev'])
1688 revs = scmutil.revrange(repo, opts['rev'])
1691 elif follow and repo.dirstate.p1() == nullid:
1689 elif follow and repo.dirstate.p1() == nullid:
1692 revs = smartset.baseset()
1690 revs = smartset.baseset()
1693 elif follow:
1691 elif follow:
1694 revs = repo.revs('reverse(:.)')
1692 revs = repo.revs('reverse(:.)')
1695 else:
1693 else:
1696 revs = smartset.spanset(repo)
1694 revs = smartset.spanset(repo)
1697 revs.reverse()
1695 revs.reverse()
1698 return revs
1696 return revs
1699
1697
1700 class FileWalkError(Exception):
1698 class FileWalkError(Exception):
1701 pass
1699 pass
1702
1700
1703 def walkfilerevs(repo, match, follow, revs, fncache):
1701 def walkfilerevs(repo, match, follow, revs, fncache):
1704 '''Walks the file history for the matched files.
1702 '''Walks the file history for the matched files.
1705
1703
1706 Returns the changeset revs that are involved in the file history.
1704 Returns the changeset revs that are involved in the file history.
1707
1705
1708 Throws FileWalkError if the file history can't be walked using
1706 Throws FileWalkError if the file history can't be walked using
1709 filelogs alone.
1707 filelogs alone.
1710 '''
1708 '''
1711 wanted = set()
1709 wanted = set()
1712 copies = []
1710 copies = []
1713 minrev, maxrev = min(revs), max(revs)
1711 minrev, maxrev = min(revs), max(revs)
1714 def filerevgen(filelog, last):
1712 def filerevgen(filelog, last):
1715 """
1713 """
1716 Only files, no patterns. Check the history of each file.
1714 Only files, no patterns. Check the history of each file.
1717
1715
1718 Examines filelog entries within minrev, maxrev linkrev range
1716 Examines filelog entries within minrev, maxrev linkrev range
1719 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1717 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1720 tuples in backwards order
1718 tuples in backwards order
1721 """
1719 """
1722 cl_count = len(repo)
1720 cl_count = len(repo)
1723 revs = []
1721 revs = []
1724 for j in xrange(0, last + 1):
1722 for j in xrange(0, last + 1):
1725 linkrev = filelog.linkrev(j)
1723 linkrev = filelog.linkrev(j)
1726 if linkrev < minrev:
1724 if linkrev < minrev:
1727 continue
1725 continue
1728 # only yield rev for which we have the changelog, it can
1726 # only yield rev for which we have the changelog, it can
1729 # happen while doing "hg log" during a pull or commit
1727 # happen while doing "hg log" during a pull or commit
1730 if linkrev >= cl_count:
1728 if linkrev >= cl_count:
1731 break
1729 break
1732
1730
1733 parentlinkrevs = []
1731 parentlinkrevs = []
1734 for p in filelog.parentrevs(j):
1732 for p in filelog.parentrevs(j):
1735 if p != nullrev:
1733 if p != nullrev:
1736 parentlinkrevs.append(filelog.linkrev(p))
1734 parentlinkrevs.append(filelog.linkrev(p))
1737 n = filelog.node(j)
1735 n = filelog.node(j)
1738 revs.append((linkrev, parentlinkrevs,
1736 revs.append((linkrev, parentlinkrevs,
1739 follow and filelog.renamed(n)))
1737 follow and filelog.renamed(n)))
1740
1738
1741 return reversed(revs)
1739 return reversed(revs)
1742 def iterfiles():
1740 def iterfiles():
1743 pctx = repo['.']
1741 pctx = repo['.']
1744 for filename in match.files():
1742 for filename in match.files():
1745 if follow:
1743 if follow:
1746 if filename not in pctx:
1744 if filename not in pctx:
1747 raise error.Abort(_('cannot follow file not in parent '
1745 raise error.Abort(_('cannot follow file not in parent '
1748 'revision: "%s"') % filename)
1746 'revision: "%s"') % filename)
1749 yield filename, pctx[filename].filenode()
1747 yield filename, pctx[filename].filenode()
1750 else:
1748 else:
1751 yield filename, None
1749 yield filename, None
1752 for filename_node in copies:
1750 for filename_node in copies:
1753 yield filename_node
1751 yield filename_node
1754
1752
1755 for file_, node in iterfiles():
1753 for file_, node in iterfiles():
1756 filelog = repo.file(file_)
1754 filelog = repo.file(file_)
1757 if not len(filelog):
1755 if not len(filelog):
1758 if node is None:
1756 if node is None:
1759 # A zero count may be a directory or deleted file, so
1757 # A zero count may be a directory or deleted file, so
1760 # try to find matching entries on the slow path.
1758 # try to find matching entries on the slow path.
1761 if follow:
1759 if follow:
1762 raise error.Abort(
1760 raise error.Abort(
1763 _('cannot follow nonexistent file: "%s"') % file_)
1761 _('cannot follow nonexistent file: "%s"') % file_)
1764 raise FileWalkError("Cannot walk via filelog")
1762 raise FileWalkError("Cannot walk via filelog")
1765 else:
1763 else:
1766 continue
1764 continue
1767
1765
1768 if node is None:
1766 if node is None:
1769 last = len(filelog) - 1
1767 last = len(filelog) - 1
1770 else:
1768 else:
1771 last = filelog.rev(node)
1769 last = filelog.rev(node)
1772
1770
1773 # keep track of all ancestors of the file
1771 # keep track of all ancestors of the file
1774 ancestors = {filelog.linkrev(last)}
1772 ancestors = {filelog.linkrev(last)}
1775
1773
1776 # iterate from latest to oldest revision
1774 # iterate from latest to oldest revision
1777 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1775 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1778 if not follow:
1776 if not follow:
1779 if rev > maxrev:
1777 if rev > maxrev:
1780 continue
1778 continue
1781 else:
1779 else:
1782 # Note that last might not be the first interesting
1780 # Note that last might not be the first interesting
1783 # rev to us:
1781 # rev to us:
1784 # if the file has been changed after maxrev, we'll
1782 # if the file has been changed after maxrev, we'll
1785 # have linkrev(last) > maxrev, and we still need
1783 # have linkrev(last) > maxrev, and we still need
1786 # to explore the file graph
1784 # to explore the file graph
1787 if rev not in ancestors:
1785 if rev not in ancestors:
1788 continue
1786 continue
1789 # XXX insert 1327 fix here
1787 # XXX insert 1327 fix here
1790 if flparentlinkrevs:
1788 if flparentlinkrevs:
1791 ancestors.update(flparentlinkrevs)
1789 ancestors.update(flparentlinkrevs)
1792
1790
1793 fncache.setdefault(rev, []).append(file_)
1791 fncache.setdefault(rev, []).append(file_)
1794 wanted.add(rev)
1792 wanted.add(rev)
1795 if copied:
1793 if copied:
1796 copies.append(copied)
1794 copies.append(copied)
1797
1795
1798 return wanted
1796 return wanted
1799
1797
1800 class _followfilter(object):
1798 class _followfilter(object):
1801 def __init__(self, repo, onlyfirst=False):
1799 def __init__(self, repo, onlyfirst=False):
1802 self.repo = repo
1800 self.repo = repo
1803 self.startrev = nullrev
1801 self.startrev = nullrev
1804 self.roots = set()
1802 self.roots = set()
1805 self.onlyfirst = onlyfirst
1803 self.onlyfirst = onlyfirst
1806
1804
1807 def match(self, rev):
1805 def match(self, rev):
1808 def realparents(rev):
1806 def realparents(rev):
1809 if self.onlyfirst:
1807 if self.onlyfirst:
1810 return self.repo.changelog.parentrevs(rev)[0:1]
1808 return self.repo.changelog.parentrevs(rev)[0:1]
1811 else:
1809 else:
1812 return filter(lambda x: x != nullrev,
1810 return filter(lambda x: x != nullrev,
1813 self.repo.changelog.parentrevs(rev))
1811 self.repo.changelog.parentrevs(rev))
1814
1812
1815 if self.startrev == nullrev:
1813 if self.startrev == nullrev:
1816 self.startrev = rev
1814 self.startrev = rev
1817 return True
1815 return True
1818
1816
1819 if rev > self.startrev:
1817 if rev > self.startrev:
1820 # forward: all descendants
1818 # forward: all descendants
1821 if not self.roots:
1819 if not self.roots:
1822 self.roots.add(self.startrev)
1820 self.roots.add(self.startrev)
1823 for parent in realparents(rev):
1821 for parent in realparents(rev):
1824 if parent in self.roots:
1822 if parent in self.roots:
1825 self.roots.add(rev)
1823 self.roots.add(rev)
1826 return True
1824 return True
1827 else:
1825 else:
1828 # backwards: all parents
1826 # backwards: all parents
1829 if not self.roots:
1827 if not self.roots:
1830 self.roots.update(realparents(self.startrev))
1828 self.roots.update(realparents(self.startrev))
1831 if rev in self.roots:
1829 if rev in self.roots:
1832 self.roots.remove(rev)
1830 self.roots.remove(rev)
1833 self.roots.update(realparents(rev))
1831 self.roots.update(realparents(rev))
1834 return True
1832 return True
1835
1833
1836 return False
1834 return False
1837
1835
1838 def walkchangerevs(repo, match, opts, prepare):
1836 def walkchangerevs(repo, match, opts, prepare):
1839 '''Iterate over files and the revs in which they changed.
1837 '''Iterate over files and the revs in which they changed.
1840
1838
1841 Callers most commonly need to iterate backwards over the history
1839 Callers most commonly need to iterate backwards over the history
1842 in which they are interested. Doing so has awful (quadratic-looking)
1840 in which they are interested. Doing so has awful (quadratic-looking)
1843 performance, so we use iterators in a "windowed" way.
1841 performance, so we use iterators in a "windowed" way.
1844
1842
1845 We walk a window of revisions in the desired order. Within the
1843 We walk a window of revisions in the desired order. Within the
1846 window, we first walk forwards to gather data, then in the desired
1844 window, we first walk forwards to gather data, then in the desired
1847 order (usually backwards) to display it.
1845 order (usually backwards) to display it.
1848
1846
1849 This function returns an iterator yielding contexts. Before
1847 This function returns an iterator yielding contexts. Before
1850 yielding each context, the iterator will first call the prepare
1848 yielding each context, the iterator will first call the prepare
1851 function on each context in the window in forward order.'''
1849 function on each context in the window in forward order.'''
1852
1850
1853 follow = opts.get('follow') or opts.get('follow_first')
1851 follow = opts.get('follow') or opts.get('follow_first')
1854 revs = _walkrevs(repo, opts)
1852 revs = _walkrevs(repo, opts)
1855 if not revs:
1853 if not revs:
1856 return []
1854 return []
1857 wanted = set()
1855 wanted = set()
1858 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1856 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1859 fncache = {}
1857 fncache = {}
1860 change = repo.__getitem__
1858 change = repo.__getitem__
1861
1859
1862 # First step is to fill wanted, the set of revisions that we want to yield.
1860 # First step is to fill wanted, the set of revisions that we want to yield.
1863 # When it does not induce extra cost, we also fill fncache for revisions in
1861 # When it does not induce extra cost, we also fill fncache for revisions in
1864 # wanted: a cache of filenames that were changed (ctx.files()) and that
1862 # wanted: a cache of filenames that were changed (ctx.files()) and that
1865 # match the file filtering conditions.
1863 # match the file filtering conditions.
1866
1864
1867 if match.always():
1865 if match.always():
1868 # No files, no patterns. Display all revs.
1866 # No files, no patterns. Display all revs.
1869 wanted = revs
1867 wanted = revs
1870 elif not slowpath:
1868 elif not slowpath:
1871 # We only have to read through the filelog to find wanted revisions
1869 # We only have to read through the filelog to find wanted revisions
1872
1870
1873 try:
1871 try:
1874 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1872 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1875 except FileWalkError:
1873 except FileWalkError:
1876 slowpath = True
1874 slowpath = True
1877
1875
1878 # We decided to fall back to the slowpath because at least one
1876 # We decided to fall back to the slowpath because at least one
1879 # of the paths was not a file. Check to see if at least one of them
1877 # of the paths was not a file. Check to see if at least one of them
1880 # existed in history, otherwise simply return
1878 # existed in history, otherwise simply return
1881 for path in match.files():
1879 for path in match.files():
1882 if path == '.' or path in repo.store:
1880 if path == '.' or path in repo.store:
1883 break
1881 break
1884 else:
1882 else:
1885 return []
1883 return []
1886
1884
1887 if slowpath:
1885 if slowpath:
1888 # We have to read the changelog to match filenames against
1886 # We have to read the changelog to match filenames against
1889 # changed files
1887 # changed files
1890
1888
1891 if follow:
1889 if follow:
1892 raise error.Abort(_('can only follow copies/renames for explicit '
1890 raise error.Abort(_('can only follow copies/renames for explicit '
1893 'filenames'))
1891 'filenames'))
1894
1892
1895 # The slow path checks files modified in every changeset.
1893 # The slow path checks files modified in every changeset.
1896 # This is really slow on large repos, so compute the set lazily.
1894 # This is really slow on large repos, so compute the set lazily.
1897 class lazywantedset(object):
1895 class lazywantedset(object):
1898 def __init__(self):
1896 def __init__(self):
1899 self.set = set()
1897 self.set = set()
1900 self.revs = set(revs)
1898 self.revs = set(revs)
1901
1899
1902 # No need to worry about locality here because it will be accessed
1900 # No need to worry about locality here because it will be accessed
1903 # in the same order as the increasing window below.
1901 # in the same order as the increasing window below.
1904 def __contains__(self, value):
1902 def __contains__(self, value):
1905 if value in self.set:
1903 if value in self.set:
1906 return True
1904 return True
1907 elif not value in self.revs:
1905 elif not value in self.revs:
1908 return False
1906 return False
1909 else:
1907 else:
1910 self.revs.discard(value)
1908 self.revs.discard(value)
1911 ctx = change(value)
1909 ctx = change(value)
1912 matches = [f for f in ctx.files() if match(f)]
1910 matches = [f for f in ctx.files() if match(f)]
1913 if matches:
1911 if matches:
1914 fncache[value] = matches
1912 fncache[value] = matches
1915 self.set.add(value)
1913 self.set.add(value)
1916 return True
1914 return True
1917 return False
1915 return False
1918
1916
1919 def discard(self, value):
1917 def discard(self, value):
1920 self.revs.discard(value)
1918 self.revs.discard(value)
1921 self.set.discard(value)
1919 self.set.discard(value)
1922
1920
1923 wanted = lazywantedset()
1921 wanted = lazywantedset()
1924
1922
1925 # it might be worthwhile to do this in the iterator if the rev range
1923 # it might be worthwhile to do this in the iterator if the rev range
1926 # is descending and the prune args are all within that range
1924 # is descending and the prune args are all within that range
1927 for rev in opts.get('prune', ()):
1925 for rev in opts.get('prune', ()):
1928 rev = repo[rev].rev()
1926 rev = repo[rev].rev()
1929 ff = _followfilter(repo)
1927 ff = _followfilter(repo)
1930 stop = min(revs[0], revs[-1])
1928 stop = min(revs[0], revs[-1])
1931 for x in xrange(rev, stop - 1, -1):
1929 for x in xrange(rev, stop - 1, -1):
1932 if ff.match(x):
1930 if ff.match(x):
1933 wanted = wanted - [x]
1931 wanted = wanted - [x]
1934
1932
1935 # Now that wanted is correctly initialized, we can iterate over the
1933 # Now that wanted is correctly initialized, we can iterate over the
1936 # revision range, yielding only revisions in wanted.
1934 # revision range, yielding only revisions in wanted.
1937 def iterate():
1935 def iterate():
1938 if follow and match.always():
1936 if follow and match.always():
1939 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1937 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1940 def want(rev):
1938 def want(rev):
1941 return ff.match(rev) and rev in wanted
1939 return ff.match(rev) and rev in wanted
1942 else:
1940 else:
1943 def want(rev):
1941 def want(rev):
1944 return rev in wanted
1942 return rev in wanted
1945
1943
1946 it = iter(revs)
1944 it = iter(revs)
1947 stopiteration = False
1945 stopiteration = False
1948 for windowsize in increasingwindows():
1946 for windowsize in increasingwindows():
1949 nrevs = []
1947 nrevs = []
1950 for i in xrange(windowsize):
1948 for i in xrange(windowsize):
1951 rev = next(it, None)
1949 rev = next(it, None)
1952 if rev is None:
1950 if rev is None:
1953 stopiteration = True
1951 stopiteration = True
1954 break
1952 break
1955 elif want(rev):
1953 elif want(rev):
1956 nrevs.append(rev)
1954 nrevs.append(rev)
1957 for rev in sorted(nrevs):
1955 for rev in sorted(nrevs):
1958 fns = fncache.get(rev)
1956 fns = fncache.get(rev)
1959 ctx = change(rev)
1957 ctx = change(rev)
1960 if not fns:
1958 if not fns:
1961 def fns_generator():
1959 def fns_generator():
1962 for f in ctx.files():
1960 for f in ctx.files():
1963 if match(f):
1961 if match(f):
1964 yield f
1962 yield f
1965 fns = fns_generator()
1963 fns = fns_generator()
1966 prepare(ctx, fns)
1964 prepare(ctx, fns)
1967 for rev in nrevs:
1965 for rev in nrevs:
1968 yield change(rev)
1966 yield change(rev)
1969
1967
1970 if stopiteration:
1968 if stopiteration:
1971 break
1969 break
1972
1970
1973 return iterate()
1971 return iterate()
1974
1972
1975 def add(ui, repo, match, prefix, explicitonly, **opts):
1973 def add(ui, repo, match, prefix, explicitonly, **opts):
1976 join = lambda f: os.path.join(prefix, f)
1974 join = lambda f: os.path.join(prefix, f)
1977 bad = []
1975 bad = []
1978
1976
1979 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
1977 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
1980 names = []
1978 names = []
1981 wctx = repo[None]
1979 wctx = repo[None]
1982 cca = None
1980 cca = None
1983 abort, warn = scmutil.checkportabilityalert(ui)
1981 abort, warn = scmutil.checkportabilityalert(ui)
1984 if abort or warn:
1982 if abort or warn:
1985 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1983 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1986
1984
1987 badmatch = matchmod.badmatch(match, badfn)
1985 badmatch = matchmod.badmatch(match, badfn)
1988 dirstate = repo.dirstate
1986 dirstate = repo.dirstate
1989 # We don't want to just call wctx.walk here, since it would return a lot of
1987 # We don't want to just call wctx.walk here, since it would return a lot of
1990 # clean files, which we aren't interested in and takes time.
1988 # clean files, which we aren't interested in and takes time.
1991 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
1989 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
1992 unknown=True, ignored=False, full=False)):
1990 unknown=True, ignored=False, full=False)):
1993 exact = match.exact(f)
1991 exact = match.exact(f)
1994 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
1992 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
1995 if cca:
1993 if cca:
1996 cca(f)
1994 cca(f)
1997 names.append(f)
1995 names.append(f)
1998 if ui.verbose or not exact:
1996 if ui.verbose or not exact:
1999 ui.status(_('adding %s\n') % match.rel(f))
1997 ui.status(_('adding %s\n') % match.rel(f))
2000
1998
2001 for subpath in sorted(wctx.substate):
1999 for subpath in sorted(wctx.substate):
2002 sub = wctx.sub(subpath)
2000 sub = wctx.sub(subpath)
2003 try:
2001 try:
2004 submatch = matchmod.subdirmatcher(subpath, match)
2002 submatch = matchmod.subdirmatcher(subpath, match)
2005 if opts.get(r'subrepos'):
2003 if opts.get(r'subrepos'):
2006 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2004 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2007 else:
2005 else:
2008 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2006 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2009 except error.LookupError:
2007 except error.LookupError:
2010 ui.status(_("skipping missing subrepository: %s\n")
2008 ui.status(_("skipping missing subrepository: %s\n")
2011 % join(subpath))
2009 % join(subpath))
2012
2010
2013 if not opts.get(r'dry_run'):
2011 if not opts.get(r'dry_run'):
2014 rejected = wctx.add(names, prefix)
2012 rejected = wctx.add(names, prefix)
2015 bad.extend(f for f in rejected if f in match.files())
2013 bad.extend(f for f in rejected if f in match.files())
2016 return bad
2014 return bad
2017
2015
2018 def addwebdirpath(repo, serverpath, webconf):
2016 def addwebdirpath(repo, serverpath, webconf):
2019 webconf[serverpath] = repo.root
2017 webconf[serverpath] = repo.root
2020 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2018 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2021
2019
2022 for r in repo.revs('filelog("path:.hgsub")'):
2020 for r in repo.revs('filelog("path:.hgsub")'):
2023 ctx = repo[r]
2021 ctx = repo[r]
2024 for subpath in ctx.substate:
2022 for subpath in ctx.substate:
2025 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2023 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2026
2024
2027 def forget(ui, repo, match, prefix, explicitonly, dryrun):
2025 def forget(ui, repo, match, prefix, explicitonly, dryrun):
2028 join = lambda f: os.path.join(prefix, f)
2026 join = lambda f: os.path.join(prefix, f)
2029 bad = []
2027 bad = []
2030 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2028 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2031 wctx = repo[None]
2029 wctx = repo[None]
2032 forgot = []
2030 forgot = []
2033
2031
2034 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2032 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2035 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2033 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2036 if explicitonly:
2034 if explicitonly:
2037 forget = [f for f in forget if match.exact(f)]
2035 forget = [f for f in forget if match.exact(f)]
2038
2036
2039 for subpath in sorted(wctx.substate):
2037 for subpath in sorted(wctx.substate):
2040 sub = wctx.sub(subpath)
2038 sub = wctx.sub(subpath)
2041 try:
2039 try:
2042 submatch = matchmod.subdirmatcher(subpath, match)
2040 submatch = matchmod.subdirmatcher(subpath, match)
2043 subbad, subforgot = sub.forget(submatch, prefix, dryrun=dryrun)
2041 subbad, subforgot = sub.forget(submatch, prefix, dryrun=dryrun)
2044 bad.extend([subpath + '/' + f for f in subbad])
2042 bad.extend([subpath + '/' + f for f in subbad])
2045 forgot.extend([subpath + '/' + f for f in subforgot])
2043 forgot.extend([subpath + '/' + f for f in subforgot])
2046 except error.LookupError:
2044 except error.LookupError:
2047 ui.status(_("skipping missing subrepository: %s\n")
2045 ui.status(_("skipping missing subrepository: %s\n")
2048 % join(subpath))
2046 % join(subpath))
2049
2047
2050 if not explicitonly:
2048 if not explicitonly:
2051 for f in match.files():
2049 for f in match.files():
2052 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2050 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2053 if f not in forgot:
2051 if f not in forgot:
2054 if repo.wvfs.exists(f):
2052 if repo.wvfs.exists(f):
2055 # Don't complain if the exact case match wasn't given.
2053 # Don't complain if the exact case match wasn't given.
2056 # But don't do this until after checking 'forgot', so
2054 # But don't do this until after checking 'forgot', so
2057 # that subrepo files aren't normalized, and this op is
2055 # that subrepo files aren't normalized, and this op is
2058 # purely from data cached by the status walk above.
2056 # purely from data cached by the status walk above.
2059 if repo.dirstate.normalize(f) in repo.dirstate:
2057 if repo.dirstate.normalize(f) in repo.dirstate:
2060 continue
2058 continue
2061 ui.warn(_('not removing %s: '
2059 ui.warn(_('not removing %s: '
2062 'file is already untracked\n')
2060 'file is already untracked\n')
2063 % match.rel(f))
2061 % match.rel(f))
2064 bad.append(f)
2062 bad.append(f)
2065
2063
2066 for f in forget:
2064 for f in forget:
2067 if ui.verbose or not match.exact(f):
2065 if ui.verbose or not match.exact(f):
2068 ui.status(_('removing %s\n') % match.rel(f))
2066 ui.status(_('removing %s\n') % match.rel(f))
2069
2067
2070 if not dryrun:
2068 if not dryrun:
2071 rejected = wctx.forget(forget, prefix)
2069 rejected = wctx.forget(forget, prefix)
2072 bad.extend(f for f in rejected if f in match.files())
2070 bad.extend(f for f in rejected if f in match.files())
2073 forgot.extend(f for f in forget if f not in rejected)
2071 forgot.extend(f for f in forget if f not in rejected)
2074 return bad, forgot
2072 return bad, forgot
2075
2073
2076 def files(ui, ctx, m, fm, fmt, subrepos):
2074 def files(ui, ctx, m, fm, fmt, subrepos):
2077 rev = ctx.rev()
2075 rev = ctx.rev()
2078 ret = 1
2076 ret = 1
2079 ds = ctx.repo().dirstate
2077 ds = ctx.repo().dirstate
2080
2078
2081 for f in ctx.matches(m):
2079 for f in ctx.matches(m):
2082 if rev is None and ds[f] == 'r':
2080 if rev is None and ds[f] == 'r':
2083 continue
2081 continue
2084 fm.startitem()
2082 fm.startitem()
2085 if ui.verbose:
2083 if ui.verbose:
2086 fc = ctx[f]
2084 fc = ctx[f]
2087 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2085 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2088 fm.data(abspath=f)
2086 fm.data(abspath=f)
2089 fm.write('path', fmt, m.rel(f))
2087 fm.write('path', fmt, m.rel(f))
2090 ret = 0
2088 ret = 0
2091
2089
2092 for subpath in sorted(ctx.substate):
2090 for subpath in sorted(ctx.substate):
2093 submatch = matchmod.subdirmatcher(subpath, m)
2091 submatch = matchmod.subdirmatcher(subpath, m)
2094 if (subrepos or m.exact(subpath) or any(submatch.files())):
2092 if (subrepos or m.exact(subpath) or any(submatch.files())):
2095 sub = ctx.sub(subpath)
2093 sub = ctx.sub(subpath)
2096 try:
2094 try:
2097 recurse = m.exact(subpath) or subrepos
2095 recurse = m.exact(subpath) or subrepos
2098 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2096 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2099 ret = 0
2097 ret = 0
2100 except error.LookupError:
2098 except error.LookupError:
2101 ui.status(_("skipping missing subrepository: %s\n")
2099 ui.status(_("skipping missing subrepository: %s\n")
2102 % m.abs(subpath))
2100 % m.abs(subpath))
2103
2101
2104 return ret
2102 return ret
2105
2103
2106 def remove(ui, repo, m, prefix, after, force, subrepos, dryrun, warnings=None):
2104 def remove(ui, repo, m, prefix, after, force, subrepos, dryrun, warnings=None):
2107 join = lambda f: os.path.join(prefix, f)
2105 join = lambda f: os.path.join(prefix, f)
2108 ret = 0
2106 ret = 0
2109 s = repo.status(match=m, clean=True)
2107 s = repo.status(match=m, clean=True)
2110 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2108 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2111
2109
2112 wctx = repo[None]
2110 wctx = repo[None]
2113
2111
2114 if warnings is None:
2112 if warnings is None:
2115 warnings = []
2113 warnings = []
2116 warn = True
2114 warn = True
2117 else:
2115 else:
2118 warn = False
2116 warn = False
2119
2117
2120 subs = sorted(wctx.substate)
2118 subs = sorted(wctx.substate)
2121 total = len(subs)
2119 total = len(subs)
2122 count = 0
2120 count = 0
2123 for subpath in subs:
2121 for subpath in subs:
2124 count += 1
2122 count += 1
2125 submatch = matchmod.subdirmatcher(subpath, m)
2123 submatch = matchmod.subdirmatcher(subpath, m)
2126 if subrepos or m.exact(subpath) or any(submatch.files()):
2124 if subrepos or m.exact(subpath) or any(submatch.files()):
2127 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2125 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2128 sub = wctx.sub(subpath)
2126 sub = wctx.sub(subpath)
2129 try:
2127 try:
2130 if sub.removefiles(submatch, prefix, after, force, subrepos,
2128 if sub.removefiles(submatch, prefix, after, force, subrepos,
2131 dryrun, warnings):
2129 dryrun, warnings):
2132 ret = 1
2130 ret = 1
2133 except error.LookupError:
2131 except error.LookupError:
2134 warnings.append(_("skipping missing subrepository: %s\n")
2132 warnings.append(_("skipping missing subrepository: %s\n")
2135 % join(subpath))
2133 % join(subpath))
2136 ui.progress(_('searching'), None)
2134 ui.progress(_('searching'), None)
2137
2135
2138 # warn about failure to delete explicit files/dirs
2136 # warn about failure to delete explicit files/dirs
2139 deleteddirs = util.dirs(deleted)
2137 deleteddirs = util.dirs(deleted)
2140 files = m.files()
2138 files = m.files()
2141 total = len(files)
2139 total = len(files)
2142 count = 0
2140 count = 0
2143 for f in files:
2141 for f in files:
2144 def insubrepo():
2142 def insubrepo():
2145 for subpath in wctx.substate:
2143 for subpath in wctx.substate:
2146 if f.startswith(subpath + '/'):
2144 if f.startswith(subpath + '/'):
2147 return True
2145 return True
2148 return False
2146 return False
2149
2147
2150 count += 1
2148 count += 1
2151 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2149 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2152 isdir = f in deleteddirs or wctx.hasdir(f)
2150 isdir = f in deleteddirs or wctx.hasdir(f)
2153 if (f in repo.dirstate or isdir or f == '.'
2151 if (f in repo.dirstate or isdir or f == '.'
2154 or insubrepo() or f in subs):
2152 or insubrepo() or f in subs):
2155 continue
2153 continue
2156
2154
2157 if repo.wvfs.exists(f):
2155 if repo.wvfs.exists(f):
2158 if repo.wvfs.isdir(f):
2156 if repo.wvfs.isdir(f):
2159 warnings.append(_('not removing %s: no tracked files\n')
2157 warnings.append(_('not removing %s: no tracked files\n')
2160 % m.rel(f))
2158 % m.rel(f))
2161 else:
2159 else:
2162 warnings.append(_('not removing %s: file is untracked\n')
2160 warnings.append(_('not removing %s: file is untracked\n')
2163 % m.rel(f))
2161 % m.rel(f))
2164 # missing files will generate a warning elsewhere
2162 # missing files will generate a warning elsewhere
2165 ret = 1
2163 ret = 1
2166 ui.progress(_('deleting'), None)
2164 ui.progress(_('deleting'), None)
2167
2165
2168 if force:
2166 if force:
2169 list = modified + deleted + clean + added
2167 list = modified + deleted + clean + added
2170 elif after:
2168 elif after:
2171 list = deleted
2169 list = deleted
2172 remaining = modified + added + clean
2170 remaining = modified + added + clean
2173 total = len(remaining)
2171 total = len(remaining)
2174 count = 0
2172 count = 0
2175 for f in remaining:
2173 for f in remaining:
2176 count += 1
2174 count += 1
2177 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2175 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2178 if ui.verbose or (f in files):
2176 if ui.verbose or (f in files):
2179 warnings.append(_('not removing %s: file still exists\n')
2177 warnings.append(_('not removing %s: file still exists\n')
2180 % m.rel(f))
2178 % m.rel(f))
2181 ret = 1
2179 ret = 1
2182 ui.progress(_('skipping'), None)
2180 ui.progress(_('skipping'), None)
2183 else:
2181 else:
2184 list = deleted + clean
2182 list = deleted + clean
2185 total = len(modified) + len(added)
2183 total = len(modified) + len(added)
2186 count = 0
2184 count = 0
2187 for f in modified:
2185 for f in modified:
2188 count += 1
2186 count += 1
2189 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2187 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2190 warnings.append(_('not removing %s: file is modified (use -f'
2188 warnings.append(_('not removing %s: file is modified (use -f'
2191 ' to force removal)\n') % m.rel(f))
2189 ' to force removal)\n') % m.rel(f))
2192 ret = 1
2190 ret = 1
2193 for f in added:
2191 for f in added:
2194 count += 1
2192 count += 1
2195 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2193 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2196 warnings.append(_("not removing %s: file has been marked for add"
2194 warnings.append(_("not removing %s: file has been marked for add"
2197 " (use 'hg forget' to undo add)\n") % m.rel(f))
2195 " (use 'hg forget' to undo add)\n") % m.rel(f))
2198 ret = 1
2196 ret = 1
2199 ui.progress(_('skipping'), None)
2197 ui.progress(_('skipping'), None)
2200
2198
2201 list = sorted(list)
2199 list = sorted(list)
2202 total = len(list)
2200 total = len(list)
2203 count = 0
2201 count = 0
2204 for f in list:
2202 for f in list:
2205 count += 1
2203 count += 1
2206 if ui.verbose or not m.exact(f):
2204 if ui.verbose or not m.exact(f):
2207 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2205 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2208 ui.status(_('removing %s\n') % m.rel(f))
2206 ui.status(_('removing %s\n') % m.rel(f))
2209 ui.progress(_('deleting'), None)
2207 ui.progress(_('deleting'), None)
2210
2208
2211 if not dryrun:
2209 if not dryrun:
2212 with repo.wlock():
2210 with repo.wlock():
2213 if not after:
2211 if not after:
2214 for f in list:
2212 for f in list:
2215 if f in added:
2213 if f in added:
2216 continue # we never unlink added files on remove
2214 continue # we never unlink added files on remove
2217 repo.wvfs.unlinkpath(f, ignoremissing=True)
2215 repo.wvfs.unlinkpath(f, ignoremissing=True)
2218 repo[None].forget(list)
2216 repo[None].forget(list)
2219
2217
2220 if warn:
2218 if warn:
2221 for warning in warnings:
2219 for warning in warnings:
2222 ui.warn(warning)
2220 ui.warn(warning)
2223
2221
2224 return ret
2222 return ret
2225
2223
2226 def _updatecatformatter(fm, ctx, matcher, path, decode):
2224 def _updatecatformatter(fm, ctx, matcher, path, decode):
2227 """Hook for adding data to the formatter used by ``hg cat``.
2225 """Hook for adding data to the formatter used by ``hg cat``.
2228
2226
2229 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2227 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2230 this method first."""
2228 this method first."""
2231 data = ctx[path].data()
2229 data = ctx[path].data()
2232 if decode:
2230 if decode:
2233 data = ctx.repo().wwritedata(path, data)
2231 data = ctx.repo().wwritedata(path, data)
2234 fm.startitem()
2232 fm.startitem()
2235 fm.write('data', '%s', data)
2233 fm.write('data', '%s', data)
2236 fm.data(abspath=path, path=matcher.rel(path))
2234 fm.data(abspath=path, path=matcher.rel(path))
2237
2235
2238 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2236 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2239 err = 1
2237 err = 1
2240 opts = pycompat.byteskwargs(opts)
2238 opts = pycompat.byteskwargs(opts)
2241
2239
2242 def write(path):
2240 def write(path):
2243 filename = None
2241 filename = None
2244 if fntemplate:
2242 if fntemplate:
2245 filename = makefilename(ctx, fntemplate,
2243 filename = makefilename(ctx, fntemplate,
2246 pathname=os.path.join(prefix, path))
2244 pathname=os.path.join(prefix, path))
2247 # attempt to create the directory if it does not already exist
2245 # attempt to create the directory if it does not already exist
2248 try:
2246 try:
2249 os.makedirs(os.path.dirname(filename))
2247 os.makedirs(os.path.dirname(filename))
2250 except OSError:
2248 except OSError:
2251 pass
2249 pass
2252 with formatter.maybereopen(basefm, filename) as fm:
2250 with formatter.maybereopen(basefm, filename) as fm:
2253 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2251 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2254
2252
2255 # Automation often uses hg cat on single files, so special case it
2253 # Automation often uses hg cat on single files, so special case it
2256 # for performance to avoid the cost of parsing the manifest.
2254 # for performance to avoid the cost of parsing the manifest.
2257 if len(matcher.files()) == 1 and not matcher.anypats():
2255 if len(matcher.files()) == 1 and not matcher.anypats():
2258 file = matcher.files()[0]
2256 file = matcher.files()[0]
2259 mfl = repo.manifestlog
2257 mfl = repo.manifestlog
2260 mfnode = ctx.manifestnode()
2258 mfnode = ctx.manifestnode()
2261 try:
2259 try:
2262 if mfnode and mfl[mfnode].find(file)[0]:
2260 if mfnode and mfl[mfnode].find(file)[0]:
2263 scmutil.fileprefetchhooks(repo, ctx, [file])
2261 scmutil.fileprefetchhooks(repo, ctx, [file])
2264 write(file)
2262 write(file)
2265 return 0
2263 return 0
2266 except KeyError:
2264 except KeyError:
2267 pass
2265 pass
2268
2266
2269 files = [f for f in ctx.walk(matcher)]
2267 files = [f for f in ctx.walk(matcher)]
2270 scmutil.fileprefetchhooks(repo, ctx, files)
2268 scmutil.fileprefetchhooks(repo, ctx, files)
2271
2269
2272 for abs in files:
2270 for abs in files:
2273 write(abs)
2271 write(abs)
2274 err = 0
2272 err = 0
2275
2273
2276 for subpath in sorted(ctx.substate):
2274 for subpath in sorted(ctx.substate):
2277 sub = ctx.sub(subpath)
2275 sub = ctx.sub(subpath)
2278 try:
2276 try:
2279 submatch = matchmod.subdirmatcher(subpath, matcher)
2277 submatch = matchmod.subdirmatcher(subpath, matcher)
2280
2278
2281 if not sub.cat(submatch, basefm, fntemplate,
2279 if not sub.cat(submatch, basefm, fntemplate,
2282 os.path.join(prefix, sub._path),
2280 os.path.join(prefix, sub._path),
2283 **pycompat.strkwargs(opts)):
2281 **pycompat.strkwargs(opts)):
2284 err = 0
2282 err = 0
2285 except error.RepoLookupError:
2283 except error.RepoLookupError:
2286 ui.status(_("skipping missing subrepository: %s\n")
2284 ui.status(_("skipping missing subrepository: %s\n")
2287 % os.path.join(prefix, subpath))
2285 % os.path.join(prefix, subpath))
2288
2286
2289 return err
2287 return err
2290
2288
2291 def commit(ui, repo, commitfunc, pats, opts):
2289 def commit(ui, repo, commitfunc, pats, opts):
2292 '''commit the specified files or all outstanding changes'''
2290 '''commit the specified files or all outstanding changes'''
2293 date = opts.get('date')
2291 date = opts.get('date')
2294 if date:
2292 if date:
2295 opts['date'] = dateutil.parsedate(date)
2293 opts['date'] = dateutil.parsedate(date)
2296 message = logmessage(ui, opts)
2294 message = logmessage(ui, opts)
2297 matcher = scmutil.match(repo[None], pats, opts)
2295 matcher = scmutil.match(repo[None], pats, opts)
2298
2296
2299 dsguard = None
2297 dsguard = None
2300 # extract addremove carefully -- this function can be called from a command
2298 # extract addremove carefully -- this function can be called from a command
2301 # that doesn't support addremove
2299 # that doesn't support addremove
2302 if opts.get('addremove'):
2300 if opts.get('addremove'):
2303 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2301 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2304 with dsguard or util.nullcontextmanager():
2302 with dsguard or util.nullcontextmanager():
2305 if dsguard:
2303 if dsguard:
2306 if scmutil.addremove(repo, matcher, "", opts) != 0:
2304 if scmutil.addremove(repo, matcher, "", opts) != 0:
2307 raise error.Abort(
2305 raise error.Abort(
2308 _("failed to mark all new/missing files as added/removed"))
2306 _("failed to mark all new/missing files as added/removed"))
2309
2307
2310 return commitfunc(ui, repo, message, matcher, opts)
2308 return commitfunc(ui, repo, message, matcher, opts)
2311
2309
2312 def samefile(f, ctx1, ctx2):
2310 def samefile(f, ctx1, ctx2):
2313 if f in ctx1.manifest():
2311 if f in ctx1.manifest():
2314 a = ctx1.filectx(f)
2312 a = ctx1.filectx(f)
2315 if f in ctx2.manifest():
2313 if f in ctx2.manifest():
2316 b = ctx2.filectx(f)
2314 b = ctx2.filectx(f)
2317 return (not a.cmp(b)
2315 return (not a.cmp(b)
2318 and a.flags() == b.flags())
2316 and a.flags() == b.flags())
2319 else:
2317 else:
2320 return False
2318 return False
2321 else:
2319 else:
2322 return f not in ctx2.manifest()
2320 return f not in ctx2.manifest()
2323
2321
2324 def amend(ui, repo, old, extra, pats, opts):
2322 def amend(ui, repo, old, extra, pats, opts):
2325 # avoid cycle context -> subrepo -> cmdutil
2323 # avoid cycle context -> subrepo -> cmdutil
2326 from . import context
2324 from . import context
2327
2325
2328 # amend will reuse the existing user if not specified, but the obsolete
2326 # amend will reuse the existing user if not specified, but the obsolete
2329 # marker creation requires that the current user's name is specified.
2327 # marker creation requires that the current user's name is specified.
2330 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2328 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2331 ui.username() # raise exception if username not set
2329 ui.username() # raise exception if username not set
2332
2330
2333 ui.note(_('amending changeset %s\n') % old)
2331 ui.note(_('amending changeset %s\n') % old)
2334 base = old.p1()
2332 base = old.p1()
2335
2333
2336 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2334 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2337 # Participating changesets:
2335 # Participating changesets:
2338 #
2336 #
2339 # wctx o - workingctx that contains changes from working copy
2337 # wctx o - workingctx that contains changes from working copy
2340 # | to go into amending commit
2338 # | to go into amending commit
2341 # |
2339 # |
2342 # old o - changeset to amend
2340 # old o - changeset to amend
2343 # |
2341 # |
2344 # base o - first parent of the changeset to amend
2342 # base o - first parent of the changeset to amend
2345 wctx = repo[None]
2343 wctx = repo[None]
2346
2344
2347 # Copy to avoid mutating input
2345 # Copy to avoid mutating input
2348 extra = extra.copy()
2346 extra = extra.copy()
2349 # Update extra dict from amended commit (e.g. to preserve graft
2347 # Update extra dict from amended commit (e.g. to preserve graft
2350 # source)
2348 # source)
2351 extra.update(old.extra())
2349 extra.update(old.extra())
2352
2350
2353 # Also update it from the from the wctx
2351 # Also update it from the from the wctx
2354 extra.update(wctx.extra())
2352 extra.update(wctx.extra())
2355
2353
2356 user = opts.get('user') or old.user()
2354 user = opts.get('user') or old.user()
2357 date = opts.get('date') or old.date()
2355 date = opts.get('date') or old.date()
2358
2356
2359 # Parse the date to allow comparison between date and old.date()
2357 # Parse the date to allow comparison between date and old.date()
2360 date = dateutil.parsedate(date)
2358 date = dateutil.parsedate(date)
2361
2359
2362 if len(old.parents()) > 1:
2360 if len(old.parents()) > 1:
2363 # ctx.files() isn't reliable for merges, so fall back to the
2361 # ctx.files() isn't reliable for merges, so fall back to the
2364 # slower repo.status() method
2362 # slower repo.status() method
2365 files = set([fn for st in repo.status(base, old)[:3]
2363 files = set([fn for st in repo.status(base, old)[:3]
2366 for fn in st])
2364 for fn in st])
2367 else:
2365 else:
2368 files = set(old.files())
2366 files = set(old.files())
2369
2367
2370 # add/remove the files to the working copy if the "addremove" option
2368 # add/remove the files to the working copy if the "addremove" option
2371 # was specified.
2369 # was specified.
2372 matcher = scmutil.match(wctx, pats, opts)
2370 matcher = scmutil.match(wctx, pats, opts)
2373 if (opts.get('addremove')
2371 if (opts.get('addremove')
2374 and scmutil.addremove(repo, matcher, "", opts)):
2372 and scmutil.addremove(repo, matcher, "", opts)):
2375 raise error.Abort(
2373 raise error.Abort(
2376 _("failed to mark all new/missing files as added/removed"))
2374 _("failed to mark all new/missing files as added/removed"))
2377
2375
2378 # Check subrepos. This depends on in-place wctx._status update in
2376 # Check subrepos. This depends on in-place wctx._status update in
2379 # subrepo.precommit(). To minimize the risk of this hack, we do
2377 # subrepo.precommit(). To minimize the risk of this hack, we do
2380 # nothing if .hgsub does not exist.
2378 # nothing if .hgsub does not exist.
2381 if '.hgsub' in wctx or '.hgsub' in old:
2379 if '.hgsub' in wctx or '.hgsub' in old:
2382 subs, commitsubs, newsubstate = subrepoutil.precommit(
2380 subs, commitsubs, newsubstate = subrepoutil.precommit(
2383 ui, wctx, wctx._status, matcher)
2381 ui, wctx, wctx._status, matcher)
2384 # amend should abort if commitsubrepos is enabled
2382 # amend should abort if commitsubrepos is enabled
2385 assert not commitsubs
2383 assert not commitsubs
2386 if subs:
2384 if subs:
2387 subrepoutil.writestate(repo, newsubstate)
2385 subrepoutil.writestate(repo, newsubstate)
2388
2386
2389 ms = mergemod.mergestate.read(repo)
2387 ms = mergemod.mergestate.read(repo)
2390 mergeutil.checkunresolved(ms)
2388 mergeutil.checkunresolved(ms)
2391
2389
2392 filestoamend = set(f for f in wctx.files() if matcher(f))
2390 filestoamend = set(f for f in wctx.files() if matcher(f))
2393
2391
2394 changes = (len(filestoamend) > 0)
2392 changes = (len(filestoamend) > 0)
2395 if changes:
2393 if changes:
2396 # Recompute copies (avoid recording a -> b -> a)
2394 # Recompute copies (avoid recording a -> b -> a)
2397 copied = copies.pathcopies(base, wctx, matcher)
2395 copied = copies.pathcopies(base, wctx, matcher)
2398 if old.p2:
2396 if old.p2:
2399 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2397 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2400
2398
2401 # Prune files which were reverted by the updates: if old
2399 # Prune files which were reverted by the updates: if old
2402 # introduced file X and the file was renamed in the working
2400 # introduced file X and the file was renamed in the working
2403 # copy, then those two files are the same and
2401 # copy, then those two files are the same and
2404 # we can discard X from our list of files. Likewise if X
2402 # we can discard X from our list of files. Likewise if X
2405 # was removed, it's no longer relevant. If X is missing (aka
2403 # was removed, it's no longer relevant. If X is missing (aka
2406 # deleted), old X must be preserved.
2404 # deleted), old X must be preserved.
2407 files.update(filestoamend)
2405 files.update(filestoamend)
2408 files = [f for f in files if (not samefile(f, wctx, base)
2406 files = [f for f in files if (not samefile(f, wctx, base)
2409 or f in wctx.deleted())]
2407 or f in wctx.deleted())]
2410
2408
2411 def filectxfn(repo, ctx_, path):
2409 def filectxfn(repo, ctx_, path):
2412 try:
2410 try:
2413 # If the file being considered is not amongst the files
2411 # If the file being considered is not amongst the files
2414 # to be amended, we should return the file context from the
2412 # to be amended, we should return the file context from the
2415 # old changeset. This avoids issues when only some files in
2413 # old changeset. This avoids issues when only some files in
2416 # the working copy are being amended but there are also
2414 # the working copy are being amended but there are also
2417 # changes to other files from the old changeset.
2415 # changes to other files from the old changeset.
2418 if path not in filestoamend:
2416 if path not in filestoamend:
2419 return old.filectx(path)
2417 return old.filectx(path)
2420
2418
2421 # Return None for removed files.
2419 # Return None for removed files.
2422 if path in wctx.removed():
2420 if path in wctx.removed():
2423 return None
2421 return None
2424
2422
2425 fctx = wctx[path]
2423 fctx = wctx[path]
2426 flags = fctx.flags()
2424 flags = fctx.flags()
2427 mctx = context.memfilectx(repo, ctx_,
2425 mctx = context.memfilectx(repo, ctx_,
2428 fctx.path(), fctx.data(),
2426 fctx.path(), fctx.data(),
2429 islink='l' in flags,
2427 islink='l' in flags,
2430 isexec='x' in flags,
2428 isexec='x' in flags,
2431 copied=copied.get(path))
2429 copied=copied.get(path))
2432 return mctx
2430 return mctx
2433 except KeyError:
2431 except KeyError:
2434 return None
2432 return None
2435 else:
2433 else:
2436 ui.note(_('copying changeset %s to %s\n') % (old, base))
2434 ui.note(_('copying changeset %s to %s\n') % (old, base))
2437
2435
2438 # Use version of files as in the old cset
2436 # Use version of files as in the old cset
2439 def filectxfn(repo, ctx_, path):
2437 def filectxfn(repo, ctx_, path):
2440 try:
2438 try:
2441 return old.filectx(path)
2439 return old.filectx(path)
2442 except KeyError:
2440 except KeyError:
2443 return None
2441 return None
2444
2442
2445 # See if we got a message from -m or -l, if not, open the editor with
2443 # See if we got a message from -m or -l, if not, open the editor with
2446 # the message of the changeset to amend.
2444 # the message of the changeset to amend.
2447 message = logmessage(ui, opts)
2445 message = logmessage(ui, opts)
2448
2446
2449 editform = mergeeditform(old, 'commit.amend')
2447 editform = mergeeditform(old, 'commit.amend')
2450 editor = getcommiteditor(editform=editform,
2448 editor = getcommiteditor(editform=editform,
2451 **pycompat.strkwargs(opts))
2449 **pycompat.strkwargs(opts))
2452
2450
2453 if not message:
2451 if not message:
2454 editor = getcommiteditor(edit=True, editform=editform)
2452 editor = getcommiteditor(edit=True, editform=editform)
2455 message = old.description()
2453 message = old.description()
2456
2454
2457 pureextra = extra.copy()
2455 pureextra = extra.copy()
2458 extra['amend_source'] = old.hex()
2456 extra['amend_source'] = old.hex()
2459
2457
2460 new = context.memctx(repo,
2458 new = context.memctx(repo,
2461 parents=[base.node(), old.p2().node()],
2459 parents=[base.node(), old.p2().node()],
2462 text=message,
2460 text=message,
2463 files=files,
2461 files=files,
2464 filectxfn=filectxfn,
2462 filectxfn=filectxfn,
2465 user=user,
2463 user=user,
2466 date=date,
2464 date=date,
2467 extra=extra,
2465 extra=extra,
2468 editor=editor)
2466 editor=editor)
2469
2467
2470 newdesc = changelog.stripdesc(new.description())
2468 newdesc = changelog.stripdesc(new.description())
2471 if ((not changes)
2469 if ((not changes)
2472 and newdesc == old.description()
2470 and newdesc == old.description()
2473 and user == old.user()
2471 and user == old.user()
2474 and date == old.date()
2472 and date == old.date()
2475 and pureextra == old.extra()):
2473 and pureextra == old.extra()):
2476 # nothing changed. continuing here would create a new node
2474 # nothing changed. continuing here would create a new node
2477 # anyway because of the amend_source noise.
2475 # anyway because of the amend_source noise.
2478 #
2476 #
2479 # This not what we expect from amend.
2477 # This not what we expect from amend.
2480 return old.node()
2478 return old.node()
2481
2479
2482 if opts.get('secret'):
2480 if opts.get('secret'):
2483 commitphase = 'secret'
2481 commitphase = 'secret'
2484 else:
2482 else:
2485 commitphase = old.phase()
2483 commitphase = old.phase()
2486 overrides = {('phases', 'new-commit'): commitphase}
2484 overrides = {('phases', 'new-commit'): commitphase}
2487 with ui.configoverride(overrides, 'amend'):
2485 with ui.configoverride(overrides, 'amend'):
2488 newid = repo.commitctx(new)
2486 newid = repo.commitctx(new)
2489
2487
2490 # Reroute the working copy parent to the new changeset
2488 # Reroute the working copy parent to the new changeset
2491 repo.setparents(newid, nullid)
2489 repo.setparents(newid, nullid)
2492 mapping = {old.node(): (newid,)}
2490 mapping = {old.node(): (newid,)}
2493 obsmetadata = None
2491 obsmetadata = None
2494 if opts.get('note'):
2492 if opts.get('note'):
2495 obsmetadata = {'note': opts['note']}
2493 obsmetadata = {'note': opts['note']}
2496 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata)
2494 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata)
2497
2495
2498 # Fixing the dirstate because localrepo.commitctx does not update
2496 # Fixing the dirstate because localrepo.commitctx does not update
2499 # it. This is rather convenient because we did not need to update
2497 # it. This is rather convenient because we did not need to update
2500 # the dirstate for all the files in the new commit which commitctx
2498 # the dirstate for all the files in the new commit which commitctx
2501 # could have done if it updated the dirstate. Now, we can
2499 # could have done if it updated the dirstate. Now, we can
2502 # selectively update the dirstate only for the amended files.
2500 # selectively update the dirstate only for the amended files.
2503 dirstate = repo.dirstate
2501 dirstate = repo.dirstate
2504
2502
2505 # Update the state of the files which were added and
2503 # Update the state of the files which were added and
2506 # and modified in the amend to "normal" in the dirstate.
2504 # and modified in the amend to "normal" in the dirstate.
2507 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2505 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2508 for f in normalfiles:
2506 for f in normalfiles:
2509 dirstate.normal(f)
2507 dirstate.normal(f)
2510
2508
2511 # Update the state of files which were removed in the amend
2509 # Update the state of files which were removed in the amend
2512 # to "removed" in the dirstate.
2510 # to "removed" in the dirstate.
2513 removedfiles = set(wctx.removed()) & filestoamend
2511 removedfiles = set(wctx.removed()) & filestoamend
2514 for f in removedfiles:
2512 for f in removedfiles:
2515 dirstate.drop(f)
2513 dirstate.drop(f)
2516
2514
2517 return newid
2515 return newid
2518
2516
2519 def commiteditor(repo, ctx, subs, editform=''):
2517 def commiteditor(repo, ctx, subs, editform=''):
2520 if ctx.description():
2518 if ctx.description():
2521 return ctx.description()
2519 return ctx.description()
2522 return commitforceeditor(repo, ctx, subs, editform=editform,
2520 return commitforceeditor(repo, ctx, subs, editform=editform,
2523 unchangedmessagedetection=True)
2521 unchangedmessagedetection=True)
2524
2522
2525 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2523 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2526 editform='', unchangedmessagedetection=False):
2524 editform='', unchangedmessagedetection=False):
2527 if not extramsg:
2525 if not extramsg:
2528 extramsg = _("Leave message empty to abort commit.")
2526 extramsg = _("Leave message empty to abort commit.")
2529
2527
2530 forms = [e for e in editform.split('.') if e]
2528 forms = [e for e in editform.split('.') if e]
2531 forms.insert(0, 'changeset')
2529 forms.insert(0, 'changeset')
2532 templatetext = None
2530 templatetext = None
2533 while forms:
2531 while forms:
2534 ref = '.'.join(forms)
2532 ref = '.'.join(forms)
2535 if repo.ui.config('committemplate', ref):
2533 if repo.ui.config('committemplate', ref):
2536 templatetext = committext = buildcommittemplate(
2534 templatetext = committext = buildcommittemplate(
2537 repo, ctx, subs, extramsg, ref)
2535 repo, ctx, subs, extramsg, ref)
2538 break
2536 break
2539 forms.pop()
2537 forms.pop()
2540 else:
2538 else:
2541 committext = buildcommittext(repo, ctx, subs, extramsg)
2539 committext = buildcommittext(repo, ctx, subs, extramsg)
2542
2540
2543 # run editor in the repository root
2541 # run editor in the repository root
2544 olddir = pycompat.getcwd()
2542 olddir = pycompat.getcwd()
2545 os.chdir(repo.root)
2543 os.chdir(repo.root)
2546
2544
2547 # make in-memory changes visible to external process
2545 # make in-memory changes visible to external process
2548 tr = repo.currenttransaction()
2546 tr = repo.currenttransaction()
2549 repo.dirstate.write(tr)
2547 repo.dirstate.write(tr)
2550 pending = tr and tr.writepending() and repo.root
2548 pending = tr and tr.writepending() and repo.root
2551
2549
2552 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2550 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2553 editform=editform, pending=pending,
2551 editform=editform, pending=pending,
2554 repopath=repo.path, action='commit')
2552 repopath=repo.path, action='commit')
2555 text = editortext
2553 text = editortext
2556
2554
2557 # strip away anything below this special string (used for editors that want
2555 # strip away anything below this special string (used for editors that want
2558 # to display the diff)
2556 # to display the diff)
2559 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2557 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2560 if stripbelow:
2558 if stripbelow:
2561 text = text[:stripbelow.start()]
2559 text = text[:stripbelow.start()]
2562
2560
2563 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2561 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2564 os.chdir(olddir)
2562 os.chdir(olddir)
2565
2563
2566 if finishdesc:
2564 if finishdesc:
2567 text = finishdesc(text)
2565 text = finishdesc(text)
2568 if not text.strip():
2566 if not text.strip():
2569 raise error.Abort(_("empty commit message"))
2567 raise error.Abort(_("empty commit message"))
2570 if unchangedmessagedetection and editortext == templatetext:
2568 if unchangedmessagedetection and editortext == templatetext:
2571 raise error.Abort(_("commit message unchanged"))
2569 raise error.Abort(_("commit message unchanged"))
2572
2570
2573 return text
2571 return text
2574
2572
2575 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2573 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2576 ui = repo.ui
2574 ui = repo.ui
2577 spec = formatter.templatespec(ref, None, None)
2575 spec = formatter.templatespec(ref, None, None)
2578 t = logcmdutil.changesettemplater(ui, repo, spec)
2576 t = logcmdutil.changesettemplater(ui, repo, spec)
2579 t.t.cache.update((k, templater.unquotestring(v))
2577 t.t.cache.update((k, templater.unquotestring(v))
2580 for k, v in repo.ui.configitems('committemplate'))
2578 for k, v in repo.ui.configitems('committemplate'))
2581
2579
2582 if not extramsg:
2580 if not extramsg:
2583 extramsg = '' # ensure that extramsg is string
2581 extramsg = '' # ensure that extramsg is string
2584
2582
2585 ui.pushbuffer()
2583 ui.pushbuffer()
2586 t.show(ctx, extramsg=extramsg)
2584 t.show(ctx, extramsg=extramsg)
2587 return ui.popbuffer()
2585 return ui.popbuffer()
2588
2586
2589 def hgprefix(msg):
2587 def hgprefix(msg):
2590 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2588 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2591
2589
2592 def buildcommittext(repo, ctx, subs, extramsg):
2590 def buildcommittext(repo, ctx, subs, extramsg):
2593 edittext = []
2591 edittext = []
2594 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2592 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2595 if ctx.description():
2593 if ctx.description():
2596 edittext.append(ctx.description())
2594 edittext.append(ctx.description())
2597 edittext.append("")
2595 edittext.append("")
2598 edittext.append("") # Empty line between message and comments.
2596 edittext.append("") # Empty line between message and comments.
2599 edittext.append(hgprefix(_("Enter commit message."
2597 edittext.append(hgprefix(_("Enter commit message."
2600 " Lines beginning with 'HG:' are removed.")))
2598 " Lines beginning with 'HG:' are removed.")))
2601 edittext.append(hgprefix(extramsg))
2599 edittext.append(hgprefix(extramsg))
2602 edittext.append("HG: --")
2600 edittext.append("HG: --")
2603 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2601 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2604 if ctx.p2():
2602 if ctx.p2():
2605 edittext.append(hgprefix(_("branch merge")))
2603 edittext.append(hgprefix(_("branch merge")))
2606 if ctx.branch():
2604 if ctx.branch():
2607 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2605 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2608 if bookmarks.isactivewdirparent(repo):
2606 if bookmarks.isactivewdirparent(repo):
2609 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2607 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2610 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2608 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2611 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2609 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2612 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2610 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2613 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2611 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2614 if not added and not modified and not removed:
2612 if not added and not modified and not removed:
2615 edittext.append(hgprefix(_("no files changed")))
2613 edittext.append(hgprefix(_("no files changed")))
2616 edittext.append("")
2614 edittext.append("")
2617
2615
2618 return "\n".join(edittext)
2616 return "\n".join(edittext)
2619
2617
2620 def commitstatus(repo, node, branch, bheads=None, opts=None):
2618 def commitstatus(repo, node, branch, bheads=None, opts=None):
2621 if opts is None:
2619 if opts is None:
2622 opts = {}
2620 opts = {}
2623 ctx = repo[node]
2621 ctx = repo[node]
2624 parents = ctx.parents()
2622 parents = ctx.parents()
2625
2623
2626 if (not opts.get('amend') and bheads and node not in bheads and not
2624 if (not opts.get('amend') and bheads and node not in bheads and not
2627 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2625 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2628 repo.ui.status(_('created new head\n'))
2626 repo.ui.status(_('created new head\n'))
2629 # The message is not printed for initial roots. For the other
2627 # The message is not printed for initial roots. For the other
2630 # changesets, it is printed in the following situations:
2628 # changesets, it is printed in the following situations:
2631 #
2629 #
2632 # Par column: for the 2 parents with ...
2630 # Par column: for the 2 parents with ...
2633 # N: null or no parent
2631 # N: null or no parent
2634 # B: parent is on another named branch
2632 # B: parent is on another named branch
2635 # C: parent is a regular non head changeset
2633 # C: parent is a regular non head changeset
2636 # H: parent was a branch head of the current branch
2634 # H: parent was a branch head of the current branch
2637 # Msg column: whether we print "created new head" message
2635 # Msg column: whether we print "created new head" message
2638 # In the following, it is assumed that there already exists some
2636 # In the following, it is assumed that there already exists some
2639 # initial branch heads of the current branch, otherwise nothing is
2637 # initial branch heads of the current branch, otherwise nothing is
2640 # printed anyway.
2638 # printed anyway.
2641 #
2639 #
2642 # Par Msg Comment
2640 # Par Msg Comment
2643 # N N y additional topo root
2641 # N N y additional topo root
2644 #
2642 #
2645 # B N y additional branch root
2643 # B N y additional branch root
2646 # C N y additional topo head
2644 # C N y additional topo head
2647 # H N n usual case
2645 # H N n usual case
2648 #
2646 #
2649 # B B y weird additional branch root
2647 # B B y weird additional branch root
2650 # C B y branch merge
2648 # C B y branch merge
2651 # H B n merge with named branch
2649 # H B n merge with named branch
2652 #
2650 #
2653 # C C y additional head from merge
2651 # C C y additional head from merge
2654 # C H n merge with a head
2652 # C H n merge with a head
2655 #
2653 #
2656 # H H n head merge: head count decreases
2654 # H H n head merge: head count decreases
2657
2655
2658 if not opts.get('close_branch'):
2656 if not opts.get('close_branch'):
2659 for r in parents:
2657 for r in parents:
2660 if r.closesbranch() and r.branch() == branch:
2658 if r.closesbranch() and r.branch() == branch:
2661 repo.ui.status(_('reopening closed branch head %d\n') % r.rev())
2659 repo.ui.status(_('reopening closed branch head %d\n') % r.rev())
2662
2660
2663 if repo.ui.debugflag:
2661 if repo.ui.debugflag:
2664 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex()))
2662 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex()))
2665 elif repo.ui.verbose:
2663 elif repo.ui.verbose:
2666 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx))
2664 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx))
2667
2665
2668 def postcommitstatus(repo, pats, opts):
2666 def postcommitstatus(repo, pats, opts):
2669 return repo.status(match=scmutil.match(repo[None], pats, opts))
2667 return repo.status(match=scmutil.match(repo[None], pats, opts))
2670
2668
2671 def revert(ui, repo, ctx, parents, *pats, **opts):
2669 def revert(ui, repo, ctx, parents, *pats, **opts):
2672 opts = pycompat.byteskwargs(opts)
2670 opts = pycompat.byteskwargs(opts)
2673 parent, p2 = parents
2671 parent, p2 = parents
2674 node = ctx.node()
2672 node = ctx.node()
2675
2673
2676 mf = ctx.manifest()
2674 mf = ctx.manifest()
2677 if node == p2:
2675 if node == p2:
2678 parent = p2
2676 parent = p2
2679
2677
2680 # need all matching names in dirstate and manifest of target rev,
2678 # need all matching names in dirstate and manifest of target rev,
2681 # so have to walk both. do not print errors if files exist in one
2679 # so have to walk both. do not print errors if files exist in one
2682 # but not other. in both cases, filesets should be evaluated against
2680 # but not other. in both cases, filesets should be evaluated against
2683 # workingctx to get consistent result (issue4497). this means 'set:**'
2681 # workingctx to get consistent result (issue4497). this means 'set:**'
2684 # cannot be used to select missing files from target rev.
2682 # cannot be used to select missing files from target rev.
2685
2683
2686 # `names` is a mapping for all elements in working copy and target revision
2684 # `names` is a mapping for all elements in working copy and target revision
2687 # The mapping is in the form:
2685 # The mapping is in the form:
2688 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2686 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2689 names = {}
2687 names = {}
2690
2688
2691 with repo.wlock():
2689 with repo.wlock():
2692 ## filling of the `names` mapping
2690 ## filling of the `names` mapping
2693 # walk dirstate to fill `names`
2691 # walk dirstate to fill `names`
2694
2692
2695 interactive = opts.get('interactive', False)
2693 interactive = opts.get('interactive', False)
2696 wctx = repo[None]
2694 wctx = repo[None]
2697 m = scmutil.match(wctx, pats, opts)
2695 m = scmutil.match(wctx, pats, opts)
2698
2696
2699 # we'll need this later
2697 # we'll need this later
2700 targetsubs = sorted(s for s in wctx.substate if m(s))
2698 targetsubs = sorted(s for s in wctx.substate if m(s))
2701
2699
2702 if not m.always():
2700 if not m.always():
2703 matcher = matchmod.badmatch(m, lambda x, y: False)
2701 matcher = matchmod.badmatch(m, lambda x, y: False)
2704 for abs in wctx.walk(matcher):
2702 for abs in wctx.walk(matcher):
2705 names[abs] = m.rel(abs), m.exact(abs)
2703 names[abs] = m.rel(abs), m.exact(abs)
2706
2704
2707 # walk target manifest to fill `names`
2705 # walk target manifest to fill `names`
2708
2706
2709 def badfn(path, msg):
2707 def badfn(path, msg):
2710 if path in names:
2708 if path in names:
2711 return
2709 return
2712 if path in ctx.substate:
2710 if path in ctx.substate:
2713 return
2711 return
2714 path_ = path + '/'
2712 path_ = path + '/'
2715 for f in names:
2713 for f in names:
2716 if f.startswith(path_):
2714 if f.startswith(path_):
2717 return
2715 return
2718 ui.warn("%s: %s\n" % (m.rel(path), msg))
2716 ui.warn("%s: %s\n" % (m.rel(path), msg))
2719
2717
2720 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2718 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2721 if abs not in names:
2719 if abs not in names:
2722 names[abs] = m.rel(abs), m.exact(abs)
2720 names[abs] = m.rel(abs), m.exact(abs)
2723
2721
2724 # Find status of all file in `names`.
2722 # Find status of all file in `names`.
2725 m = scmutil.matchfiles(repo, names)
2723 m = scmutil.matchfiles(repo, names)
2726
2724
2727 changes = repo.status(node1=node, match=m,
2725 changes = repo.status(node1=node, match=m,
2728 unknown=True, ignored=True, clean=True)
2726 unknown=True, ignored=True, clean=True)
2729 else:
2727 else:
2730 changes = repo.status(node1=node, match=m)
2728 changes = repo.status(node1=node, match=m)
2731 for kind in changes:
2729 for kind in changes:
2732 for abs in kind:
2730 for abs in kind:
2733 names[abs] = m.rel(abs), m.exact(abs)
2731 names[abs] = m.rel(abs), m.exact(abs)
2734
2732
2735 m = scmutil.matchfiles(repo, names)
2733 m = scmutil.matchfiles(repo, names)
2736
2734
2737 modified = set(changes.modified)
2735 modified = set(changes.modified)
2738 added = set(changes.added)
2736 added = set(changes.added)
2739 removed = set(changes.removed)
2737 removed = set(changes.removed)
2740 _deleted = set(changes.deleted)
2738 _deleted = set(changes.deleted)
2741 unknown = set(changes.unknown)
2739 unknown = set(changes.unknown)
2742 unknown.update(changes.ignored)
2740 unknown.update(changes.ignored)
2743 clean = set(changes.clean)
2741 clean = set(changes.clean)
2744 modadded = set()
2742 modadded = set()
2745
2743
2746 # We need to account for the state of the file in the dirstate,
2744 # We need to account for the state of the file in the dirstate,
2747 # even when we revert against something else than parent. This will
2745 # even when we revert against something else than parent. This will
2748 # slightly alter the behavior of revert (doing back up or not, delete
2746 # slightly alter the behavior of revert (doing back up or not, delete
2749 # or just forget etc).
2747 # or just forget etc).
2750 if parent == node:
2748 if parent == node:
2751 dsmodified = modified
2749 dsmodified = modified
2752 dsadded = added
2750 dsadded = added
2753 dsremoved = removed
2751 dsremoved = removed
2754 # store all local modifications, useful later for rename detection
2752 # store all local modifications, useful later for rename detection
2755 localchanges = dsmodified | dsadded
2753 localchanges = dsmodified | dsadded
2756 modified, added, removed = set(), set(), set()
2754 modified, added, removed = set(), set(), set()
2757 else:
2755 else:
2758 changes = repo.status(node1=parent, match=m)
2756 changes = repo.status(node1=parent, match=m)
2759 dsmodified = set(changes.modified)
2757 dsmodified = set(changes.modified)
2760 dsadded = set(changes.added)
2758 dsadded = set(changes.added)
2761 dsremoved = set(changes.removed)
2759 dsremoved = set(changes.removed)
2762 # store all local modifications, useful later for rename detection
2760 # store all local modifications, useful later for rename detection
2763 localchanges = dsmodified | dsadded
2761 localchanges = dsmodified | dsadded
2764
2762
2765 # only take into account for removes between wc and target
2763 # only take into account for removes between wc and target
2766 clean |= dsremoved - removed
2764 clean |= dsremoved - removed
2767 dsremoved &= removed
2765 dsremoved &= removed
2768 # distinct between dirstate remove and other
2766 # distinct between dirstate remove and other
2769 removed -= dsremoved
2767 removed -= dsremoved
2770
2768
2771 modadded = added & dsmodified
2769 modadded = added & dsmodified
2772 added -= modadded
2770 added -= modadded
2773
2771
2774 # tell newly modified apart.
2772 # tell newly modified apart.
2775 dsmodified &= modified
2773 dsmodified &= modified
2776 dsmodified |= modified & dsadded # dirstate added may need backup
2774 dsmodified |= modified & dsadded # dirstate added may need backup
2777 modified -= dsmodified
2775 modified -= dsmodified
2778
2776
2779 # We need to wait for some post-processing to update this set
2777 # We need to wait for some post-processing to update this set
2780 # before making the distinction. The dirstate will be used for
2778 # before making the distinction. The dirstate will be used for
2781 # that purpose.
2779 # that purpose.
2782 dsadded = added
2780 dsadded = added
2783
2781
2784 # in case of merge, files that are actually added can be reported as
2782 # in case of merge, files that are actually added can be reported as
2785 # modified, we need to post process the result
2783 # modified, we need to post process the result
2786 if p2 != nullid:
2784 if p2 != nullid:
2787 mergeadd = set(dsmodified)
2785 mergeadd = set(dsmodified)
2788 for path in dsmodified:
2786 for path in dsmodified:
2789 if path in mf:
2787 if path in mf:
2790 mergeadd.remove(path)
2788 mergeadd.remove(path)
2791 dsadded |= mergeadd
2789 dsadded |= mergeadd
2792 dsmodified -= mergeadd
2790 dsmodified -= mergeadd
2793
2791
2794 # if f is a rename, update `names` to also revert the source
2792 # if f is a rename, update `names` to also revert the source
2795 cwd = repo.getcwd()
2793 cwd = repo.getcwd()
2796 for f in localchanges:
2794 for f in localchanges:
2797 src = repo.dirstate.copied(f)
2795 src = repo.dirstate.copied(f)
2798 # XXX should we check for rename down to target node?
2796 # XXX should we check for rename down to target node?
2799 if src and src not in names and repo.dirstate[src] == 'r':
2797 if src and src not in names and repo.dirstate[src] == 'r':
2800 dsremoved.add(src)
2798 dsremoved.add(src)
2801 names[src] = (repo.pathto(src, cwd), True)
2799 names[src] = (repo.pathto(src, cwd), True)
2802
2800
2803 # determine the exact nature of the deleted changesets
2801 # determine the exact nature of the deleted changesets
2804 deladded = set(_deleted)
2802 deladded = set(_deleted)
2805 for path in _deleted:
2803 for path in _deleted:
2806 if path in mf:
2804 if path in mf:
2807 deladded.remove(path)
2805 deladded.remove(path)
2808 deleted = _deleted - deladded
2806 deleted = _deleted - deladded
2809
2807
2810 # distinguish between file to forget and the other
2808 # distinguish between file to forget and the other
2811 added = set()
2809 added = set()
2812 for abs in dsadded:
2810 for abs in dsadded:
2813 if repo.dirstate[abs] != 'a':
2811 if repo.dirstate[abs] != 'a':
2814 added.add(abs)
2812 added.add(abs)
2815 dsadded -= added
2813 dsadded -= added
2816
2814
2817 for abs in deladded:
2815 for abs in deladded:
2818 if repo.dirstate[abs] == 'a':
2816 if repo.dirstate[abs] == 'a':
2819 dsadded.add(abs)
2817 dsadded.add(abs)
2820 deladded -= dsadded
2818 deladded -= dsadded
2821
2819
2822 # For files marked as removed, we check if an unknown file is present at
2820 # For files marked as removed, we check if an unknown file is present at
2823 # the same path. If a such file exists it may need to be backed up.
2821 # the same path. If a such file exists it may need to be backed up.
2824 # Making the distinction at this stage helps have simpler backup
2822 # Making the distinction at this stage helps have simpler backup
2825 # logic.
2823 # logic.
2826 removunk = set()
2824 removunk = set()
2827 for abs in removed:
2825 for abs in removed:
2828 target = repo.wjoin(abs)
2826 target = repo.wjoin(abs)
2829 if os.path.lexists(target):
2827 if os.path.lexists(target):
2830 removunk.add(abs)
2828 removunk.add(abs)
2831 removed -= removunk
2829 removed -= removunk
2832
2830
2833 dsremovunk = set()
2831 dsremovunk = set()
2834 for abs in dsremoved:
2832 for abs in dsremoved:
2835 target = repo.wjoin(abs)
2833 target = repo.wjoin(abs)
2836 if os.path.lexists(target):
2834 if os.path.lexists(target):
2837 dsremovunk.add(abs)
2835 dsremovunk.add(abs)
2838 dsremoved -= dsremovunk
2836 dsremoved -= dsremovunk
2839
2837
2840 # action to be actually performed by revert
2838 # action to be actually performed by revert
2841 # (<list of file>, message>) tuple
2839 # (<list of file>, message>) tuple
2842 actions = {'revert': ([], _('reverting %s\n')),
2840 actions = {'revert': ([], _('reverting %s\n')),
2843 'add': ([], _('adding %s\n')),
2841 'add': ([], _('adding %s\n')),
2844 'remove': ([], _('removing %s\n')),
2842 'remove': ([], _('removing %s\n')),
2845 'drop': ([], _('removing %s\n')),
2843 'drop': ([], _('removing %s\n')),
2846 'forget': ([], _('forgetting %s\n')),
2844 'forget': ([], _('forgetting %s\n')),
2847 'undelete': ([], _('undeleting %s\n')),
2845 'undelete': ([], _('undeleting %s\n')),
2848 'noop': (None, _('no changes needed to %s\n')),
2846 'noop': (None, _('no changes needed to %s\n')),
2849 'unknown': (None, _('file not managed: %s\n')),
2847 'unknown': (None, _('file not managed: %s\n')),
2850 }
2848 }
2851
2849
2852 # "constant" that convey the backup strategy.
2850 # "constant" that convey the backup strategy.
2853 # All set to `discard` if `no-backup` is set do avoid checking
2851 # All set to `discard` if `no-backup` is set do avoid checking
2854 # no_backup lower in the code.
2852 # no_backup lower in the code.
2855 # These values are ordered for comparison purposes
2853 # These values are ordered for comparison purposes
2856 backupinteractive = 3 # do backup if interactively modified
2854 backupinteractive = 3 # do backup if interactively modified
2857 backup = 2 # unconditionally do backup
2855 backup = 2 # unconditionally do backup
2858 check = 1 # check if the existing file differs from target
2856 check = 1 # check if the existing file differs from target
2859 discard = 0 # never do backup
2857 discard = 0 # never do backup
2860 if opts.get('no_backup'):
2858 if opts.get('no_backup'):
2861 backupinteractive = backup = check = discard
2859 backupinteractive = backup = check = discard
2862 if interactive:
2860 if interactive:
2863 dsmodifiedbackup = backupinteractive
2861 dsmodifiedbackup = backupinteractive
2864 else:
2862 else:
2865 dsmodifiedbackup = backup
2863 dsmodifiedbackup = backup
2866 tobackup = set()
2864 tobackup = set()
2867
2865
2868 backupanddel = actions['remove']
2866 backupanddel = actions['remove']
2869 if not opts.get('no_backup'):
2867 if not opts.get('no_backup'):
2870 backupanddel = actions['drop']
2868 backupanddel = actions['drop']
2871
2869
2872 disptable = (
2870 disptable = (
2873 # dispatch table:
2871 # dispatch table:
2874 # file state
2872 # file state
2875 # action
2873 # action
2876 # make backup
2874 # make backup
2877
2875
2878 ## Sets that results that will change file on disk
2876 ## Sets that results that will change file on disk
2879 # Modified compared to target, no local change
2877 # Modified compared to target, no local change
2880 (modified, actions['revert'], discard),
2878 (modified, actions['revert'], discard),
2881 # Modified compared to target, but local file is deleted
2879 # Modified compared to target, but local file is deleted
2882 (deleted, actions['revert'], discard),
2880 (deleted, actions['revert'], discard),
2883 # Modified compared to target, local change
2881 # Modified compared to target, local change
2884 (dsmodified, actions['revert'], dsmodifiedbackup),
2882 (dsmodified, actions['revert'], dsmodifiedbackup),
2885 # Added since target
2883 # Added since target
2886 (added, actions['remove'], discard),
2884 (added, actions['remove'], discard),
2887 # Added in working directory
2885 # Added in working directory
2888 (dsadded, actions['forget'], discard),
2886 (dsadded, actions['forget'], discard),
2889 # Added since target, have local modification
2887 # Added since target, have local modification
2890 (modadded, backupanddel, backup),
2888 (modadded, backupanddel, backup),
2891 # Added since target but file is missing in working directory
2889 # Added since target but file is missing in working directory
2892 (deladded, actions['drop'], discard),
2890 (deladded, actions['drop'], discard),
2893 # Removed since target, before working copy parent
2891 # Removed since target, before working copy parent
2894 (removed, actions['add'], discard),
2892 (removed, actions['add'], discard),
2895 # Same as `removed` but an unknown file exists at the same path
2893 # Same as `removed` but an unknown file exists at the same path
2896 (removunk, actions['add'], check),
2894 (removunk, actions['add'], check),
2897 # Removed since targe, marked as such in working copy parent
2895 # Removed since targe, marked as such in working copy parent
2898 (dsremoved, actions['undelete'], discard),
2896 (dsremoved, actions['undelete'], discard),
2899 # Same as `dsremoved` but an unknown file exists at the same path
2897 # Same as `dsremoved` but an unknown file exists at the same path
2900 (dsremovunk, actions['undelete'], check),
2898 (dsremovunk, actions['undelete'], check),
2901 ## the following sets does not result in any file changes
2899 ## the following sets does not result in any file changes
2902 # File with no modification
2900 # File with no modification
2903 (clean, actions['noop'], discard),
2901 (clean, actions['noop'], discard),
2904 # Existing file, not tracked anywhere
2902 # Existing file, not tracked anywhere
2905 (unknown, actions['unknown'], discard),
2903 (unknown, actions['unknown'], discard),
2906 )
2904 )
2907
2905
2908 for abs, (rel, exact) in sorted(names.items()):
2906 for abs, (rel, exact) in sorted(names.items()):
2909 # target file to be touch on disk (relative to cwd)
2907 # target file to be touch on disk (relative to cwd)
2910 target = repo.wjoin(abs)
2908 target = repo.wjoin(abs)
2911 # search the entry in the dispatch table.
2909 # search the entry in the dispatch table.
2912 # if the file is in any of these sets, it was touched in the working
2910 # if the file is in any of these sets, it was touched in the working
2913 # directory parent and we are sure it needs to be reverted.
2911 # directory parent and we are sure it needs to be reverted.
2914 for table, (xlist, msg), dobackup in disptable:
2912 for table, (xlist, msg), dobackup in disptable:
2915 if abs not in table:
2913 if abs not in table:
2916 continue
2914 continue
2917 if xlist is not None:
2915 if xlist is not None:
2918 xlist.append(abs)
2916 xlist.append(abs)
2919 if dobackup:
2917 if dobackup:
2920 # If in interactive mode, don't automatically create
2918 # If in interactive mode, don't automatically create
2921 # .orig files (issue4793)
2919 # .orig files (issue4793)
2922 if dobackup == backupinteractive:
2920 if dobackup == backupinteractive:
2923 tobackup.add(abs)
2921 tobackup.add(abs)
2924 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
2922 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
2925 bakname = scmutil.origpath(ui, repo, rel)
2923 bakname = scmutil.origpath(ui, repo, rel)
2926 ui.note(_('saving current version of %s as %s\n') %
2924 ui.note(_('saving current version of %s as %s\n') %
2927 (rel, bakname))
2925 (rel, bakname))
2928 if not opts.get('dry_run'):
2926 if not opts.get('dry_run'):
2929 if interactive:
2927 if interactive:
2930 util.copyfile(target, bakname)
2928 util.copyfile(target, bakname)
2931 else:
2929 else:
2932 util.rename(target, bakname)
2930 util.rename(target, bakname)
2933 if ui.verbose or not exact:
2931 if ui.verbose or not exact:
2934 if not isinstance(msg, bytes):
2932 if not isinstance(msg, bytes):
2935 msg = msg(abs)
2933 msg = msg(abs)
2936 ui.status(msg % rel)
2934 ui.status(msg % rel)
2937 elif exact:
2935 elif exact:
2938 ui.warn(msg % rel)
2936 ui.warn(msg % rel)
2939 break
2937 break
2940
2938
2941 if not opts.get('dry_run'):
2939 if not opts.get('dry_run'):
2942 needdata = ('revert', 'add', 'undelete')
2940 needdata = ('revert', 'add', 'undelete')
2943 if _revertprefetch is not _revertprefetchstub:
2941 if _revertprefetch is not _revertprefetchstub:
2944 ui.deprecwarn("'cmdutil._revertprefetch' is deprecated, "
2942 ui.deprecwarn("'cmdutil._revertprefetch' is deprecated, "
2945 "add a callback to 'scmutil.fileprefetchhooks'",
2943 "add a callback to 'scmutil.fileprefetchhooks'",
2946 '4.6', stacklevel=1)
2944 '4.6', stacklevel=1)
2947 _revertprefetch(repo, ctx,
2945 _revertprefetch(repo, ctx,
2948 *[actions[name][0] for name in needdata])
2946 *[actions[name][0] for name in needdata])
2949 oplist = [actions[name][0] for name in needdata]
2947 oplist = [actions[name][0] for name in needdata]
2950 prefetch = scmutil.fileprefetchhooks
2948 prefetch = scmutil.fileprefetchhooks
2951 prefetch(repo, ctx, [f for sublist in oplist for f in sublist])
2949 prefetch(repo, ctx, [f for sublist in oplist for f in sublist])
2952 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
2950 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
2953
2951
2954 if targetsubs:
2952 if targetsubs:
2955 # Revert the subrepos on the revert list
2953 # Revert the subrepos on the revert list
2956 for sub in targetsubs:
2954 for sub in targetsubs:
2957 try:
2955 try:
2958 wctx.sub(sub).revert(ctx.substate[sub], *pats,
2956 wctx.sub(sub).revert(ctx.substate[sub], *pats,
2959 **pycompat.strkwargs(opts))
2957 **pycompat.strkwargs(opts))
2960 except KeyError:
2958 except KeyError:
2961 raise error.Abort("subrepository '%s' does not exist in %s!"
2959 raise error.Abort("subrepository '%s' does not exist in %s!"
2962 % (sub, short(ctx.node())))
2960 % (sub, short(ctx.node())))
2963
2961
2964 def _revertprefetchstub(repo, ctx, *files):
2962 def _revertprefetchstub(repo, ctx, *files):
2965 """Stub method for detecting extension wrapping of _revertprefetch(), to
2963 """Stub method for detecting extension wrapping of _revertprefetch(), to
2966 issue a deprecation warning."""
2964 issue a deprecation warning."""
2967
2965
2968 _revertprefetch = _revertprefetchstub
2966 _revertprefetch = _revertprefetchstub
2969
2967
2970 def _performrevert(repo, parents, ctx, actions, interactive=False,
2968 def _performrevert(repo, parents, ctx, actions, interactive=False,
2971 tobackup=None):
2969 tobackup=None):
2972 """function that actually perform all the actions computed for revert
2970 """function that actually perform all the actions computed for revert
2973
2971
2974 This is an independent function to let extension to plug in and react to
2972 This is an independent function to let extension to plug in and react to
2975 the imminent revert.
2973 the imminent revert.
2976
2974
2977 Make sure you have the working directory locked when calling this function.
2975 Make sure you have the working directory locked when calling this function.
2978 """
2976 """
2979 parent, p2 = parents
2977 parent, p2 = parents
2980 node = ctx.node()
2978 node = ctx.node()
2981 excluded_files = []
2979 excluded_files = []
2982
2980
2983 def checkout(f):
2981 def checkout(f):
2984 fc = ctx[f]
2982 fc = ctx[f]
2985 repo.wwrite(f, fc.data(), fc.flags())
2983 repo.wwrite(f, fc.data(), fc.flags())
2986
2984
2987 def doremove(f):
2985 def doremove(f):
2988 try:
2986 try:
2989 repo.wvfs.unlinkpath(f)
2987 repo.wvfs.unlinkpath(f)
2990 except OSError:
2988 except OSError:
2991 pass
2989 pass
2992 repo.dirstate.remove(f)
2990 repo.dirstate.remove(f)
2993
2991
2994 audit_path = pathutil.pathauditor(repo.root, cached=True)
2992 audit_path = pathutil.pathauditor(repo.root, cached=True)
2995 for f in actions['forget'][0]:
2993 for f in actions['forget'][0]:
2996 if interactive:
2994 if interactive:
2997 choice = repo.ui.promptchoice(
2995 choice = repo.ui.promptchoice(
2998 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
2996 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
2999 if choice == 0:
2997 if choice == 0:
3000 repo.dirstate.drop(f)
2998 repo.dirstate.drop(f)
3001 else:
2999 else:
3002 excluded_files.append(f)
3000 excluded_files.append(f)
3003 else:
3001 else:
3004 repo.dirstate.drop(f)
3002 repo.dirstate.drop(f)
3005 for f in actions['remove'][0]:
3003 for f in actions['remove'][0]:
3006 audit_path(f)
3004 audit_path(f)
3007 if interactive:
3005 if interactive:
3008 choice = repo.ui.promptchoice(
3006 choice = repo.ui.promptchoice(
3009 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3007 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3010 if choice == 0:
3008 if choice == 0:
3011 doremove(f)
3009 doremove(f)
3012 else:
3010 else:
3013 excluded_files.append(f)
3011 excluded_files.append(f)
3014 else:
3012 else:
3015 doremove(f)
3013 doremove(f)
3016 for f in actions['drop'][0]:
3014 for f in actions['drop'][0]:
3017 audit_path(f)
3015 audit_path(f)
3018 repo.dirstate.remove(f)
3016 repo.dirstate.remove(f)
3019
3017
3020 normal = None
3018 normal = None
3021 if node == parent:
3019 if node == parent:
3022 # We're reverting to our parent. If possible, we'd like status
3020 # We're reverting to our parent. If possible, we'd like status
3023 # to report the file as clean. We have to use normallookup for
3021 # to report the file as clean. We have to use normallookup for
3024 # merges to avoid losing information about merged/dirty files.
3022 # merges to avoid losing information about merged/dirty files.
3025 if p2 != nullid:
3023 if p2 != nullid:
3026 normal = repo.dirstate.normallookup
3024 normal = repo.dirstate.normallookup
3027 else:
3025 else:
3028 normal = repo.dirstate.normal
3026 normal = repo.dirstate.normal
3029
3027
3030 newlyaddedandmodifiedfiles = set()
3028 newlyaddedandmodifiedfiles = set()
3031 if interactive:
3029 if interactive:
3032 # Prompt the user for changes to revert
3030 # Prompt the user for changes to revert
3033 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
3031 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
3034 m = scmutil.matchfiles(repo, torevert)
3032 m = scmutil.matchfiles(repo, torevert)
3035 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3033 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3036 diffopts.nodates = True
3034 diffopts.nodates = True
3037 diffopts.git = True
3035 diffopts.git = True
3038 operation = 'discard'
3036 operation = 'discard'
3039 reversehunks = True
3037 reversehunks = True
3040 if node != parent:
3038 if node != parent:
3041 operation = 'apply'
3039 operation = 'apply'
3042 reversehunks = False
3040 reversehunks = False
3043 if reversehunks:
3041 if reversehunks:
3044 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3042 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3045 else:
3043 else:
3046 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3044 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3047 originalchunks = patch.parsepatch(diff)
3045 originalchunks = patch.parsepatch(diff)
3048
3046
3049 try:
3047 try:
3050
3048
3051 chunks, opts = recordfilter(repo.ui, originalchunks,
3049 chunks, opts = recordfilter(repo.ui, originalchunks,
3052 operation=operation)
3050 operation=operation)
3053 if reversehunks:
3051 if reversehunks:
3054 chunks = patch.reversehunks(chunks)
3052 chunks = patch.reversehunks(chunks)
3055
3053
3056 except error.PatchError as err:
3054 except error.PatchError as err:
3057 raise error.Abort(_('error parsing patch: %s') % err)
3055 raise error.Abort(_('error parsing patch: %s') % err)
3058
3056
3059 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3057 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3060 if tobackup is None:
3058 if tobackup is None:
3061 tobackup = set()
3059 tobackup = set()
3062 # Apply changes
3060 # Apply changes
3063 fp = stringio()
3061 fp = stringio()
3064 for c in chunks:
3062 for c in chunks:
3065 # Create a backup file only if this hunk should be backed up
3063 # Create a backup file only if this hunk should be backed up
3066 if ishunk(c) and c.header.filename() in tobackup:
3064 if ishunk(c) and c.header.filename() in tobackup:
3067 abs = c.header.filename()
3065 abs = c.header.filename()
3068 target = repo.wjoin(abs)
3066 target = repo.wjoin(abs)
3069 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3067 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3070 util.copyfile(target, bakname)
3068 util.copyfile(target, bakname)
3071 tobackup.remove(abs)
3069 tobackup.remove(abs)
3072 c.write(fp)
3070 c.write(fp)
3073 dopatch = fp.tell()
3071 dopatch = fp.tell()
3074 fp.seek(0)
3072 fp.seek(0)
3075 if dopatch:
3073 if dopatch:
3076 try:
3074 try:
3077 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3075 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3078 except error.PatchError as err:
3076 except error.PatchError as err:
3079 raise error.Abort(pycompat.bytestr(err))
3077 raise error.Abort(pycompat.bytestr(err))
3080 del fp
3078 del fp
3081 else:
3079 else:
3082 for f in actions['revert'][0]:
3080 for f in actions['revert'][0]:
3083 checkout(f)
3081 checkout(f)
3084 if normal:
3082 if normal:
3085 normal(f)
3083 normal(f)
3086
3084
3087 for f in actions['add'][0]:
3085 for f in actions['add'][0]:
3088 # Don't checkout modified files, they are already created by the diff
3086 # Don't checkout modified files, they are already created by the diff
3089 if f not in newlyaddedandmodifiedfiles:
3087 if f not in newlyaddedandmodifiedfiles:
3090 checkout(f)
3088 checkout(f)
3091 repo.dirstate.add(f)
3089 repo.dirstate.add(f)
3092
3090
3093 normal = repo.dirstate.normallookup
3091 normal = repo.dirstate.normallookup
3094 if node == parent and p2 == nullid:
3092 if node == parent and p2 == nullid:
3095 normal = repo.dirstate.normal
3093 normal = repo.dirstate.normal
3096 for f in actions['undelete'][0]:
3094 for f in actions['undelete'][0]:
3097 checkout(f)
3095 checkout(f)
3098 normal(f)
3096 normal(f)
3099
3097
3100 copied = copies.pathcopies(repo[parent], ctx)
3098 copied = copies.pathcopies(repo[parent], ctx)
3101
3099
3102 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3100 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3103 if f in copied:
3101 if f in copied:
3104 repo.dirstate.copy(copied[f], f)
3102 repo.dirstate.copy(copied[f], f)
3105
3103
3106 class command(registrar.command):
3104 class command(registrar.command):
3107 """deprecated: used registrar.command instead"""
3105 """deprecated: used registrar.command instead"""
3108 def _doregister(self, func, name, *args, **kwargs):
3106 def _doregister(self, func, name, *args, **kwargs):
3109 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3107 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3110 return super(command, self)._doregister(func, name, *args, **kwargs)
3108 return super(command, self)._doregister(func, name, *args, **kwargs)
3111
3109
3112 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3110 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3113 # commands.outgoing. "missing" is "missing" of the result of
3111 # commands.outgoing. "missing" is "missing" of the result of
3114 # "findcommonoutgoing()"
3112 # "findcommonoutgoing()"
3115 outgoinghooks = util.hooks()
3113 outgoinghooks = util.hooks()
3116
3114
3117 # a list of (ui, repo) functions called by commands.summary
3115 # a list of (ui, repo) functions called by commands.summary
3118 summaryhooks = util.hooks()
3116 summaryhooks = util.hooks()
3119
3117
3120 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3118 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3121 #
3119 #
3122 # functions should return tuple of booleans below, if 'changes' is None:
3120 # functions should return tuple of booleans below, if 'changes' is None:
3123 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3121 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3124 #
3122 #
3125 # otherwise, 'changes' is a tuple of tuples below:
3123 # otherwise, 'changes' is a tuple of tuples below:
3126 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3124 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3127 # - (desturl, destbranch, destpeer, outgoing)
3125 # - (desturl, destbranch, destpeer, outgoing)
3128 summaryremotehooks = util.hooks()
3126 summaryremotehooks = util.hooks()
3129
3127
3130 # A list of state files kept by multistep operations like graft.
3128 # A list of state files kept by multistep operations like graft.
3131 # Since graft cannot be aborted, it is considered 'clearable' by update.
3129 # Since graft cannot be aborted, it is considered 'clearable' by update.
3132 # note: bisect is intentionally excluded
3130 # note: bisect is intentionally excluded
3133 # (state file, clearable, allowcommit, error, hint)
3131 # (state file, clearable, allowcommit, error, hint)
3134 unfinishedstates = [
3132 unfinishedstates = [
3135 ('graftstate', True, False, _('graft in progress'),
3133 ('graftstate', True, False, _('graft in progress'),
3136 _("use 'hg graft --continue' or 'hg update' to abort")),
3134 _("use 'hg graft --continue' or 'hg update' to abort")),
3137 ('updatestate', True, False, _('last update was interrupted'),
3135 ('updatestate', True, False, _('last update was interrupted'),
3138 _("use 'hg update' to get a consistent checkout"))
3136 _("use 'hg update' to get a consistent checkout"))
3139 ]
3137 ]
3140
3138
3141 def checkunfinished(repo, commit=False):
3139 def checkunfinished(repo, commit=False):
3142 '''Look for an unfinished multistep operation, like graft, and abort
3140 '''Look for an unfinished multistep operation, like graft, and abort
3143 if found. It's probably good to check this right before
3141 if found. It's probably good to check this right before
3144 bailifchanged().
3142 bailifchanged().
3145 '''
3143 '''
3146 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3144 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3147 if commit and allowcommit:
3145 if commit and allowcommit:
3148 continue
3146 continue
3149 if repo.vfs.exists(f):
3147 if repo.vfs.exists(f):
3150 raise error.Abort(msg, hint=hint)
3148 raise error.Abort(msg, hint=hint)
3151
3149
3152 def clearunfinished(repo):
3150 def clearunfinished(repo):
3153 '''Check for unfinished operations (as above), and clear the ones
3151 '''Check for unfinished operations (as above), and clear the ones
3154 that are clearable.
3152 that are clearable.
3155 '''
3153 '''
3156 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3154 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3157 if not clearable and repo.vfs.exists(f):
3155 if not clearable and repo.vfs.exists(f):
3158 raise error.Abort(msg, hint=hint)
3156 raise error.Abort(msg, hint=hint)
3159 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3157 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3160 if clearable and repo.vfs.exists(f):
3158 if clearable and repo.vfs.exists(f):
3161 util.unlink(repo.vfs.join(f))
3159 util.unlink(repo.vfs.join(f))
3162
3160
3163 afterresolvedstates = [
3161 afterresolvedstates = [
3164 ('graftstate',
3162 ('graftstate',
3165 _('hg graft --continue')),
3163 _('hg graft --continue')),
3166 ]
3164 ]
3167
3165
3168 def howtocontinue(repo):
3166 def howtocontinue(repo):
3169 '''Check for an unfinished operation and return the command to finish
3167 '''Check for an unfinished operation and return the command to finish
3170 it.
3168 it.
3171
3169
3172 afterresolvedstates tuples define a .hg/{file} and the corresponding
3170 afterresolvedstates tuples define a .hg/{file} and the corresponding
3173 command needed to finish it.
3171 command needed to finish it.
3174
3172
3175 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3173 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3176 a boolean.
3174 a boolean.
3177 '''
3175 '''
3178 contmsg = _("continue: %s")
3176 contmsg = _("continue: %s")
3179 for f, msg in afterresolvedstates:
3177 for f, msg in afterresolvedstates:
3180 if repo.vfs.exists(f):
3178 if repo.vfs.exists(f):
3181 return contmsg % msg, True
3179 return contmsg % msg, True
3182 if repo[None].dirty(missing=True, merge=False, branch=False):
3180 if repo[None].dirty(missing=True, merge=False, branch=False):
3183 return contmsg % _("hg commit"), False
3181 return contmsg % _("hg commit"), False
3184 return None, None
3182 return None, None
3185
3183
3186 def checkafterresolved(repo):
3184 def checkafterresolved(repo):
3187 '''Inform the user about the next action after completing hg resolve
3185 '''Inform the user about the next action after completing hg resolve
3188
3186
3189 If there's a matching afterresolvedstates, howtocontinue will yield
3187 If there's a matching afterresolvedstates, howtocontinue will yield
3190 repo.ui.warn as the reporter.
3188 repo.ui.warn as the reporter.
3191
3189
3192 Otherwise, it will yield repo.ui.note.
3190 Otherwise, it will yield repo.ui.note.
3193 '''
3191 '''
3194 msg, warning = howtocontinue(repo)
3192 msg, warning = howtocontinue(repo)
3195 if msg is not None:
3193 if msg is not None:
3196 if warning:
3194 if warning:
3197 repo.ui.warn("%s\n" % msg)
3195 repo.ui.warn("%s\n" % msg)
3198 else:
3196 else:
3199 repo.ui.note("%s\n" % msg)
3197 repo.ui.note("%s\n" % msg)
3200
3198
3201 def wrongtooltocontinue(repo, task):
3199 def wrongtooltocontinue(repo, task):
3202 '''Raise an abort suggesting how to properly continue if there is an
3200 '''Raise an abort suggesting how to properly continue if there is an
3203 active task.
3201 active task.
3204
3202
3205 Uses howtocontinue() to find the active task.
3203 Uses howtocontinue() to find the active task.
3206
3204
3207 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3205 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3208 a hint.
3206 a hint.
3209 '''
3207 '''
3210 after = howtocontinue(repo)
3208 after = howtocontinue(repo)
3211 hint = None
3209 hint = None
3212 if after[1]:
3210 if after[1]:
3213 hint = after[0]
3211 hint = after[0]
3214 raise error.Abort(_('no %s in progress') % task, hint=hint)
3212 raise error.Abort(_('no %s in progress') % task, hint=hint)
3215
3213
3216 class changeset_printer(logcmdutil.changesetprinter):
3214 class changeset_printer(logcmdutil.changesetprinter):
3217
3215
3218 def __init__(self, ui, *args, **kwargs):
3216 def __init__(self, ui, *args, **kwargs):
3219 msg = ("'cmdutil.changeset_printer' is deprecated, "
3217 msg = ("'cmdutil.changeset_printer' is deprecated, "
3220 "use 'logcmdutil.logcmdutil'")
3218 "use 'logcmdutil.logcmdutil'")
3221 ui.deprecwarn(msg, "4.6")
3219 ui.deprecwarn(msg, "4.6")
3222 super(changeset_printer, self).__init__(ui, *args, **kwargs)
3220 super(changeset_printer, self).__init__(ui, *args, **kwargs)
3223
3221
3224 def displaygraph(ui, *args, **kwargs):
3222 def displaygraph(ui, *args, **kwargs):
3225 msg = ("'cmdutil.displaygraph' is deprecated, "
3223 msg = ("'cmdutil.displaygraph' is deprecated, "
3226 "use 'logcmdutil.displaygraph'")
3224 "use 'logcmdutil.displaygraph'")
3227 ui.deprecwarn(msg, "4.6")
3225 ui.deprecwarn(msg, "4.6")
3228 return logcmdutil.displaygraph(ui, *args, **kwargs)
3226 return logcmdutil.displaygraph(ui, *args, **kwargs)
3229
3227
3230 def show_changeset(ui, *args, **kwargs):
3228 def show_changeset(ui, *args, **kwargs):
3231 msg = ("'cmdutil.show_changeset' is deprecated, "
3229 msg = ("'cmdutil.show_changeset' is deprecated, "
3232 "use 'logcmdutil.changesetdisplayer'")
3230 "use 'logcmdutil.changesetdisplayer'")
3233 ui.deprecwarn(msg, "4.6")
3231 ui.deprecwarn(msg, "4.6")
3234 return logcmdutil.changesetdisplayer(ui, *args, **kwargs)
3232 return logcmdutil.changesetdisplayer(ui, *args, **kwargs)
@@ -1,5653 +1,5652 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 logcmdutil,
44 logcmdutil,
45 merge as mergemod,
45 merge as mergemod,
46 obsolete,
46 obsolete,
47 obsutil,
47 obsutil,
48 patch,
48 patch,
49 phases,
49 phases,
50 pycompat,
50 pycompat,
51 rcutil,
51 rcutil,
52 registrar,
52 registrar,
53 revsetlang,
53 revsetlang,
54 rewriteutil,
54 rewriteutil,
55 scmutil,
55 scmutil,
56 server,
56 server,
57 streamclone,
57 streamclone,
58 tags as tagsmod,
58 tags as tagsmod,
59 templatekw,
59 templatekw,
60 ui as uimod,
60 ui as uimod,
61 util,
61 util,
62 wireprotoserver,
62 wireprotoserver,
63 )
63 )
64 from .utils import (
64 from .utils import (
65 dateutil,
65 dateutil,
66 procutil,
66 procutil,
67 stringutil,
67 stringutil,
68 )
68 )
69
69
70 release = lockmod.release
70 release = lockmod.release
71
71
72 table = {}
72 table = {}
73 table.update(debugcommandsmod.command._table)
73 table.update(debugcommandsmod.command._table)
74
74
75 command = registrar.command(table)
75 command = registrar.command(table)
76 readonly = registrar.command.readonly
76 readonly = registrar.command.readonly
77
77
78 # common command options
78 # common command options
79
79
80 globalopts = [
80 globalopts = [
81 ('R', 'repository', '',
81 ('R', 'repository', '',
82 _('repository root directory or name of overlay bundle file'),
82 _('repository root directory or name of overlay bundle file'),
83 _('REPO')),
83 _('REPO')),
84 ('', 'cwd', '',
84 ('', 'cwd', '',
85 _('change working directory'), _('DIR')),
85 _('change working directory'), _('DIR')),
86 ('y', 'noninteractive', None,
86 ('y', 'noninteractive', None,
87 _('do not prompt, automatically pick the first choice for all prompts')),
87 _('do not prompt, automatically pick the first choice for all prompts')),
88 ('q', 'quiet', None, _('suppress output')),
88 ('q', 'quiet', None, _('suppress output')),
89 ('v', 'verbose', None, _('enable additional output')),
89 ('v', 'verbose', None, _('enable additional output')),
90 ('', 'color', '',
90 ('', 'color', '',
91 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
91 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
92 # and should not be translated
92 # and should not be translated
93 _("when to colorize (boolean, always, auto, never, or debug)"),
93 _("when to colorize (boolean, always, auto, never, or debug)"),
94 _('TYPE')),
94 _('TYPE')),
95 ('', 'config', [],
95 ('', 'config', [],
96 _('set/override config option (use \'section.name=value\')'),
96 _('set/override config option (use \'section.name=value\')'),
97 _('CONFIG')),
97 _('CONFIG')),
98 ('', 'debug', None, _('enable debugging output')),
98 ('', 'debug', None, _('enable debugging output')),
99 ('', 'debugger', None, _('start debugger')),
99 ('', 'debugger', None, _('start debugger')),
100 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
100 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
101 _('ENCODE')),
101 _('ENCODE')),
102 ('', 'encodingmode', encoding.encodingmode,
102 ('', 'encodingmode', encoding.encodingmode,
103 _('set the charset encoding mode'), _('MODE')),
103 _('set the charset encoding mode'), _('MODE')),
104 ('', 'traceback', None, _('always print a traceback on exception')),
104 ('', 'traceback', None, _('always print a traceback on exception')),
105 ('', 'time', None, _('time how long the command takes')),
105 ('', 'time', None, _('time how long the command takes')),
106 ('', 'profile', None, _('print command execution profile')),
106 ('', 'profile', None, _('print command execution profile')),
107 ('', 'version', None, _('output version information and exit')),
107 ('', 'version', None, _('output version information and exit')),
108 ('h', 'help', None, _('display help and exit')),
108 ('h', 'help', None, _('display help and exit')),
109 ('', 'hidden', False, _('consider hidden changesets')),
109 ('', 'hidden', False, _('consider hidden changesets')),
110 ('', 'pager', 'auto',
110 ('', 'pager', 'auto',
111 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
111 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
112 ]
112 ]
113
113
114 dryrunopts = cmdutil.dryrunopts
114 dryrunopts = cmdutil.dryrunopts
115 remoteopts = cmdutil.remoteopts
115 remoteopts = cmdutil.remoteopts
116 walkopts = cmdutil.walkopts
116 walkopts = cmdutil.walkopts
117 commitopts = cmdutil.commitopts
117 commitopts = cmdutil.commitopts
118 commitopts2 = cmdutil.commitopts2
118 commitopts2 = cmdutil.commitopts2
119 formatteropts = cmdutil.formatteropts
119 formatteropts = cmdutil.formatteropts
120 templateopts = cmdutil.templateopts
120 templateopts = cmdutil.templateopts
121 logopts = cmdutil.logopts
121 logopts = cmdutil.logopts
122 diffopts = cmdutil.diffopts
122 diffopts = cmdutil.diffopts
123 diffwsopts = cmdutil.diffwsopts
123 diffwsopts = cmdutil.diffwsopts
124 diffopts2 = cmdutil.diffopts2
124 diffopts2 = cmdutil.diffopts2
125 mergetoolopts = cmdutil.mergetoolopts
125 mergetoolopts = cmdutil.mergetoolopts
126 similarityopts = cmdutil.similarityopts
126 similarityopts = cmdutil.similarityopts
127 subrepoopts = cmdutil.subrepoopts
127 subrepoopts = cmdutil.subrepoopts
128 debugrevlogopts = cmdutil.debugrevlogopts
128 debugrevlogopts = cmdutil.debugrevlogopts
129
129
130 # Commands start here, listed alphabetically
130 # Commands start here, listed alphabetically
131
131
132 @command('^add',
132 @command('^add',
133 walkopts + subrepoopts + dryrunopts,
133 walkopts + subrepoopts + dryrunopts,
134 _('[OPTION]... [FILE]...'),
134 _('[OPTION]... [FILE]...'),
135 inferrepo=True)
135 inferrepo=True)
136 def add(ui, repo, *pats, **opts):
136 def add(ui, repo, *pats, **opts):
137 """add the specified files on the next commit
137 """add the specified files on the next commit
138
138
139 Schedule files to be version controlled and added to the
139 Schedule files to be version controlled and added to the
140 repository.
140 repository.
141
141
142 The files will be added to the repository at the next commit. To
142 The files will be added to the repository at the next commit. To
143 undo an add before that, see :hg:`forget`.
143 undo an add before that, see :hg:`forget`.
144
144
145 If no names are given, add all files to the repository (except
145 If no names are given, add all files to the repository (except
146 files matching ``.hgignore``).
146 files matching ``.hgignore``).
147
147
148 .. container:: verbose
148 .. container:: verbose
149
149
150 Examples:
150 Examples:
151
151
152 - New (unknown) files are added
152 - New (unknown) files are added
153 automatically by :hg:`add`::
153 automatically by :hg:`add`::
154
154
155 $ ls
155 $ ls
156 foo.c
156 foo.c
157 $ hg status
157 $ hg status
158 ? foo.c
158 ? foo.c
159 $ hg add
159 $ hg add
160 adding foo.c
160 adding foo.c
161 $ hg status
161 $ hg status
162 A foo.c
162 A foo.c
163
163
164 - Specific files to be added can be specified::
164 - Specific files to be added can be specified::
165
165
166 $ ls
166 $ ls
167 bar.c foo.c
167 bar.c foo.c
168 $ hg status
168 $ hg status
169 ? bar.c
169 ? bar.c
170 ? foo.c
170 ? foo.c
171 $ hg add bar.c
171 $ hg add bar.c
172 $ hg status
172 $ hg status
173 A bar.c
173 A bar.c
174 ? foo.c
174 ? foo.c
175
175
176 Returns 0 if all files are successfully added.
176 Returns 0 if all files are successfully added.
177 """
177 """
178
178
179 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
179 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
180 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
180 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
181 return rejected and 1 or 0
181 return rejected and 1 or 0
182
182
183 @command('addremove',
183 @command('addremove',
184 similarityopts + subrepoopts + walkopts + dryrunopts,
184 similarityopts + subrepoopts + walkopts + dryrunopts,
185 _('[OPTION]... [FILE]...'),
185 _('[OPTION]... [FILE]...'),
186 inferrepo=True)
186 inferrepo=True)
187 def addremove(ui, repo, *pats, **opts):
187 def addremove(ui, repo, *pats, **opts):
188 """add all new files, delete all missing files
188 """add all new files, delete all missing files
189
189
190 Add all new files and remove all missing files from the
190 Add all new files and remove all missing files from the
191 repository.
191 repository.
192
192
193 Unless names are given, new files are ignored if they match any of
193 Unless names are given, new files are ignored if they match any of
194 the patterns in ``.hgignore``. As with add, these changes take
194 the patterns in ``.hgignore``. As with add, these changes take
195 effect at the next commit.
195 effect at the next commit.
196
196
197 Use the -s/--similarity option to detect renamed files. This
197 Use the -s/--similarity option to detect renamed files. This
198 option takes a percentage between 0 (disabled) and 100 (files must
198 option takes a percentage between 0 (disabled) and 100 (files must
199 be identical) as its parameter. With a parameter greater than 0,
199 be identical) as its parameter. With a parameter greater than 0,
200 this compares every removed file with every added file and records
200 this compares every removed file with every added file and records
201 those similar enough as renames. Detecting renamed files this way
201 those similar enough as renames. Detecting renamed files this way
202 can be expensive. After using this option, :hg:`status -C` can be
202 can be expensive. After using this option, :hg:`status -C` can be
203 used to check which files were identified as moved or renamed. If
203 used to check which files were identified as moved or renamed. If
204 not specified, -s/--similarity defaults to 100 and only renames of
204 not specified, -s/--similarity defaults to 100 and only renames of
205 identical files are detected.
205 identical files are detected.
206
206
207 .. container:: verbose
207 .. container:: verbose
208
208
209 Examples:
209 Examples:
210
210
211 - A number of files (bar.c and foo.c) are new,
211 - A number of files (bar.c and foo.c) are new,
212 while foobar.c has been removed (without using :hg:`remove`)
212 while foobar.c has been removed (without using :hg:`remove`)
213 from the repository::
213 from the repository::
214
214
215 $ ls
215 $ ls
216 bar.c foo.c
216 bar.c foo.c
217 $ hg status
217 $ hg status
218 ! foobar.c
218 ! foobar.c
219 ? bar.c
219 ? bar.c
220 ? foo.c
220 ? foo.c
221 $ hg addremove
221 $ hg addremove
222 adding bar.c
222 adding bar.c
223 adding foo.c
223 adding foo.c
224 removing foobar.c
224 removing foobar.c
225 $ hg status
225 $ hg status
226 A bar.c
226 A bar.c
227 A foo.c
227 A foo.c
228 R foobar.c
228 R foobar.c
229
229
230 - A file foobar.c was moved to foo.c without using :hg:`rename`.
230 - A file foobar.c was moved to foo.c without using :hg:`rename`.
231 Afterwards, it was edited slightly::
231 Afterwards, it was edited slightly::
232
232
233 $ ls
233 $ ls
234 foo.c
234 foo.c
235 $ hg status
235 $ hg status
236 ! foobar.c
236 ! foobar.c
237 ? foo.c
237 ? foo.c
238 $ hg addremove --similarity 90
238 $ hg addremove --similarity 90
239 removing foobar.c
239 removing foobar.c
240 adding foo.c
240 adding foo.c
241 recording removal of foobar.c as rename to foo.c (94% similar)
241 recording removal of foobar.c as rename to foo.c (94% similar)
242 $ hg status -C
242 $ hg status -C
243 A foo.c
243 A foo.c
244 foobar.c
244 foobar.c
245 R foobar.c
245 R foobar.c
246
246
247 Returns 0 if all files are successfully added.
247 Returns 0 if all files are successfully added.
248 """
248 """
249 opts = pycompat.byteskwargs(opts)
249 opts = pycompat.byteskwargs(opts)
250 if not opts.get('similarity'):
250 if not opts.get('similarity'):
251 opts['similarity'] = '100'
251 opts['similarity'] = '100'
252 matcher = scmutil.match(repo[None], pats, opts)
252 matcher = scmutil.match(repo[None], pats, opts)
253 return scmutil.addremove(repo, matcher, "", opts)
253 return scmutil.addremove(repo, matcher, "", opts)
254
254
255 @command('^annotate|blame',
255 @command('^annotate|blame',
256 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
256 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
257 ('', 'follow', None,
257 ('', 'follow', None,
258 _('follow copies/renames and list the filename (DEPRECATED)')),
258 _('follow copies/renames and list the filename (DEPRECATED)')),
259 ('', 'no-follow', None, _("don't follow copies and renames")),
259 ('', 'no-follow', None, _("don't follow copies and renames")),
260 ('a', 'text', None, _('treat all files as text')),
260 ('a', 'text', None, _('treat all files as text')),
261 ('u', 'user', None, _('list the author (long with -v)')),
261 ('u', 'user', None, _('list the author (long with -v)')),
262 ('f', 'file', None, _('list the filename')),
262 ('f', 'file', None, _('list the filename')),
263 ('d', 'date', None, _('list the date (short with -q)')),
263 ('d', 'date', None, _('list the date (short with -q)')),
264 ('n', 'number', None, _('list the revision number (default)')),
264 ('n', 'number', None, _('list the revision number (default)')),
265 ('c', 'changeset', None, _('list the changeset')),
265 ('c', 'changeset', None, _('list the changeset')),
266 ('l', 'line-number', None, _('show line number at the first appearance')),
266 ('l', 'line-number', None, _('show line number at the first appearance')),
267 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
267 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
268 ] + diffwsopts + walkopts + formatteropts,
268 ] + diffwsopts + walkopts + formatteropts,
269 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
269 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
270 inferrepo=True)
270 inferrepo=True)
271 def annotate(ui, repo, *pats, **opts):
271 def annotate(ui, repo, *pats, **opts):
272 """show changeset information by line for each file
272 """show changeset information by line for each file
273
273
274 List changes in files, showing the revision id responsible for
274 List changes in files, showing the revision id responsible for
275 each line.
275 each line.
276
276
277 This command is useful for discovering when a change was made and
277 This command is useful for discovering when a change was made and
278 by whom.
278 by whom.
279
279
280 If you include --file, --user, or --date, the revision number is
280 If you include --file, --user, or --date, the revision number is
281 suppressed unless you also include --number.
281 suppressed unless you also include --number.
282
282
283 Without the -a/--text option, annotate will avoid processing files
283 Without the -a/--text option, annotate will avoid processing files
284 it detects as binary. With -a, annotate will annotate the file
284 it detects as binary. With -a, annotate will annotate the file
285 anyway, although the results will probably be neither useful
285 anyway, although the results will probably be neither useful
286 nor desirable.
286 nor desirable.
287
287
288 Returns 0 on success.
288 Returns 0 on success.
289 """
289 """
290 opts = pycompat.byteskwargs(opts)
290 opts = pycompat.byteskwargs(opts)
291 if not pats:
291 if not pats:
292 raise error.Abort(_('at least one filename or pattern is required'))
292 raise error.Abort(_('at least one filename or pattern is required'))
293
293
294 if opts.get('follow'):
294 if opts.get('follow'):
295 # --follow is deprecated and now just an alias for -f/--file
295 # --follow is deprecated and now just an alias for -f/--file
296 # to mimic the behavior of Mercurial before version 1.5
296 # to mimic the behavior of Mercurial before version 1.5
297 opts['file'] = True
297 opts['file'] = True
298
298
299 rev = opts.get('rev')
299 rev = opts.get('rev')
300 if rev:
300 if rev:
301 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
301 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
302 ctx = scmutil.revsingle(repo, rev)
302 ctx = scmutil.revsingle(repo, rev)
303
303
304 rootfm = ui.formatter('annotate', opts)
304 rootfm = ui.formatter('annotate', opts)
305 if ui.quiet:
305 if ui.quiet:
306 datefunc = dateutil.shortdate
306 datefunc = dateutil.shortdate
307 else:
307 else:
308 datefunc = dateutil.datestr
308 datefunc = dateutil.datestr
309 if ctx.rev() is None:
309 if ctx.rev() is None:
310 def hexfn(node):
310 def hexfn(node):
311 if node is None:
311 if node is None:
312 return None
312 return None
313 else:
313 else:
314 return rootfm.hexfunc(node)
314 return rootfm.hexfunc(node)
315 if opts.get('changeset'):
315 if opts.get('changeset'):
316 # omit "+" suffix which is appended to node hex
316 # omit "+" suffix which is appended to node hex
317 def formatrev(rev):
317 def formatrev(rev):
318 if rev is None:
318 if rev is None:
319 return '%d' % ctx.p1().rev()
319 return '%d' % ctx.p1().rev()
320 else:
320 else:
321 return '%d' % rev
321 return '%d' % rev
322 else:
322 else:
323 def formatrev(rev):
323 def formatrev(rev):
324 if rev is None:
324 if rev is None:
325 return '%d+' % ctx.p1().rev()
325 return '%d+' % ctx.p1().rev()
326 else:
326 else:
327 return '%d ' % rev
327 return '%d ' % rev
328 def formathex(hex):
328 def formathex(hex):
329 if hex is None:
329 if hex is None:
330 return '%s+' % rootfm.hexfunc(ctx.p1().node())
330 return '%s+' % rootfm.hexfunc(ctx.p1().node())
331 else:
331 else:
332 return '%s ' % hex
332 return '%s ' % hex
333 else:
333 else:
334 hexfn = rootfm.hexfunc
334 hexfn = rootfm.hexfunc
335 formatrev = formathex = pycompat.bytestr
335 formatrev = formathex = pycompat.bytestr
336
336
337 opmap = [('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
337 opmap = [('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
338 ('number', ' ', lambda x: x.fctx.rev(), formatrev),
338 ('number', ' ', lambda x: x.fctx.rev(), formatrev),
339 ('changeset', ' ', lambda x: hexfn(x.fctx.node()), formathex),
339 ('changeset', ' ', lambda x: hexfn(x.fctx.node()), formathex),
340 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
340 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
341 ('file', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
341 ('file', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
342 ('line_number', ':', lambda x: x.lineno, pycompat.bytestr),
342 ('line_number', ':', lambda x: x.lineno, pycompat.bytestr),
343 ]
343 ]
344 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
344 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
345
345
346 if (not opts.get('user') and not opts.get('changeset')
346 if (not opts.get('user') and not opts.get('changeset')
347 and not opts.get('date') and not opts.get('file')):
347 and not opts.get('date') and not opts.get('file')):
348 opts['number'] = True
348 opts['number'] = True
349
349
350 linenumber = opts.get('line_number') is not None
350 linenumber = opts.get('line_number') is not None
351 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
351 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
352 raise error.Abort(_('at least one of -n/-c is required for -l'))
352 raise error.Abort(_('at least one of -n/-c is required for -l'))
353
353
354 ui.pager('annotate')
354 ui.pager('annotate')
355
355
356 if rootfm.isplain():
356 if rootfm.isplain():
357 def makefunc(get, fmt):
357 def makefunc(get, fmt):
358 return lambda x: fmt(get(x))
358 return lambda x: fmt(get(x))
359 else:
359 else:
360 def makefunc(get, fmt):
360 def makefunc(get, fmt):
361 return get
361 return get
362 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
362 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
363 if opts.get(op)]
363 if opts.get(op)]
364 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
364 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
365 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
365 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
366 if opts.get(op))
366 if opts.get(op))
367
367
368 def bad(x, y):
368 def bad(x, y):
369 raise error.Abort("%s: %s" % (x, y))
369 raise error.Abort("%s: %s" % (x, y))
370
370
371 m = scmutil.match(ctx, pats, opts, badfn=bad)
371 m = scmutil.match(ctx, pats, opts, badfn=bad)
372
372
373 follow = not opts.get('no_follow')
373 follow = not opts.get('no_follow')
374 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
374 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
375 whitespace=True)
375 whitespace=True)
376 skiprevs = opts.get('skip')
376 skiprevs = opts.get('skip')
377 if skiprevs:
377 if skiprevs:
378 skiprevs = scmutil.revrange(repo, skiprevs)
378 skiprevs = scmutil.revrange(repo, skiprevs)
379
379
380 for abs in ctx.walk(m):
380 for abs in ctx.walk(m):
381 fctx = ctx[abs]
381 fctx = ctx[abs]
382 rootfm.startitem()
382 rootfm.startitem()
383 rootfm.data(abspath=abs, path=m.rel(abs))
383 rootfm.data(abspath=abs, path=m.rel(abs))
384 if not opts.get('text') and fctx.isbinary():
384 if not opts.get('text') and fctx.isbinary():
385 rootfm.plain(_("%s: binary file\n")
385 rootfm.plain(_("%s: binary file\n")
386 % ((pats and m.rel(abs)) or abs))
386 % ((pats and m.rel(abs)) or abs))
387 continue
387 continue
388
388
389 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
389 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
390 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
390 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
391 diffopts=diffopts)
391 diffopts=diffopts)
392 if not lines:
392 if not lines:
393 fm.end()
393 fm.end()
394 continue
394 continue
395 formats = []
395 formats = []
396 pieces = []
396 pieces = []
397
397
398 for f, sep in funcmap:
398 for f, sep in funcmap:
399 l = [f(n) for n in lines]
399 l = [f(n) for n in lines]
400 if fm.isplain():
400 if fm.isplain():
401 sizes = [encoding.colwidth(x) for x in l]
401 sizes = [encoding.colwidth(x) for x in l]
402 ml = max(sizes)
402 ml = max(sizes)
403 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
403 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
404 else:
404 else:
405 formats.append(['%s' for x in l])
405 formats.append(['%s' for x in l])
406 pieces.append(l)
406 pieces.append(l)
407
407
408 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
408 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
409 fm.startitem()
409 fm.startitem()
410 fm.context(fctx=n.fctx)
410 fm.context(fctx=n.fctx)
411 fm.write(fields, "".join(f), *p)
411 fm.write(fields, "".join(f), *p)
412 if n.skip:
412 if n.skip:
413 fmt = "* %s"
413 fmt = "* %s"
414 else:
414 else:
415 fmt = ": %s"
415 fmt = ": %s"
416 fm.write('line', fmt, n.text)
416 fm.write('line', fmt, n.text)
417
417
418 if not lines[-1].text.endswith('\n'):
418 if not lines[-1].text.endswith('\n'):
419 fm.plain('\n')
419 fm.plain('\n')
420 fm.end()
420 fm.end()
421
421
422 rootfm.end()
422 rootfm.end()
423
423
424 @command('archive',
424 @command('archive',
425 [('', 'no-decode', None, _('do not pass files through decoders')),
425 [('', 'no-decode', None, _('do not pass files through decoders')),
426 ('p', 'prefix', '', _('directory prefix for files in archive'),
426 ('p', 'prefix', '', _('directory prefix for files in archive'),
427 _('PREFIX')),
427 _('PREFIX')),
428 ('r', 'rev', '', _('revision to distribute'), _('REV')),
428 ('r', 'rev', '', _('revision to distribute'), _('REV')),
429 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
429 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
430 ] + subrepoopts + walkopts,
430 ] + subrepoopts + walkopts,
431 _('[OPTION]... DEST'))
431 _('[OPTION]... DEST'))
432 def archive(ui, repo, dest, **opts):
432 def archive(ui, repo, dest, **opts):
433 '''create an unversioned archive of a repository revision
433 '''create an unversioned archive of a repository revision
434
434
435 By default, the revision used is the parent of the working
435 By default, the revision used is the parent of the working
436 directory; use -r/--rev to specify a different revision.
436 directory; use -r/--rev to specify a different revision.
437
437
438 The archive type is automatically detected based on file
438 The archive type is automatically detected based on file
439 extension (to override, use -t/--type).
439 extension (to override, use -t/--type).
440
440
441 .. container:: verbose
441 .. container:: verbose
442
442
443 Examples:
443 Examples:
444
444
445 - create a zip file containing the 1.0 release::
445 - create a zip file containing the 1.0 release::
446
446
447 hg archive -r 1.0 project-1.0.zip
447 hg archive -r 1.0 project-1.0.zip
448
448
449 - create a tarball excluding .hg files::
449 - create a tarball excluding .hg files::
450
450
451 hg archive project.tar.gz -X ".hg*"
451 hg archive project.tar.gz -X ".hg*"
452
452
453 Valid types are:
453 Valid types are:
454
454
455 :``files``: a directory full of files (default)
455 :``files``: a directory full of files (default)
456 :``tar``: tar archive, uncompressed
456 :``tar``: tar archive, uncompressed
457 :``tbz2``: tar archive, compressed using bzip2
457 :``tbz2``: tar archive, compressed using bzip2
458 :``tgz``: tar archive, compressed using gzip
458 :``tgz``: tar archive, compressed using gzip
459 :``uzip``: zip archive, uncompressed
459 :``uzip``: zip archive, uncompressed
460 :``zip``: zip archive, compressed using deflate
460 :``zip``: zip archive, compressed using deflate
461
461
462 The exact name of the destination archive or directory is given
462 The exact name of the destination archive or directory is given
463 using a format string; see :hg:`help export` for details.
463 using a format string; see :hg:`help export` for details.
464
464
465 Each member added to an archive file has a directory prefix
465 Each member added to an archive file has a directory prefix
466 prepended. Use -p/--prefix to specify a format string for the
466 prepended. Use -p/--prefix to specify a format string for the
467 prefix. The default is the basename of the archive, with suffixes
467 prefix. The default is the basename of the archive, with suffixes
468 removed.
468 removed.
469
469
470 Returns 0 on success.
470 Returns 0 on success.
471 '''
471 '''
472
472
473 opts = pycompat.byteskwargs(opts)
473 opts = pycompat.byteskwargs(opts)
474 rev = opts.get('rev')
474 rev = opts.get('rev')
475 if rev:
475 if rev:
476 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
476 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
477 ctx = scmutil.revsingle(repo, rev)
477 ctx = scmutil.revsingle(repo, rev)
478 if not ctx:
478 if not ctx:
479 raise error.Abort(_('no working directory: please specify a revision'))
479 raise error.Abort(_('no working directory: please specify a revision'))
480 node = ctx.node()
480 node = ctx.node()
481 dest = cmdutil.makefilename(ctx, dest)
481 dest = cmdutil.makefilename(ctx, dest)
482 if os.path.realpath(dest) == repo.root:
482 if os.path.realpath(dest) == repo.root:
483 raise error.Abort(_('repository root cannot be destination'))
483 raise error.Abort(_('repository root cannot be destination'))
484
484
485 kind = opts.get('type') or archival.guesskind(dest) or 'files'
485 kind = opts.get('type') or archival.guesskind(dest) or 'files'
486 prefix = opts.get('prefix')
486 prefix = opts.get('prefix')
487
487
488 if dest == '-':
488 if dest == '-':
489 if kind == 'files':
489 if kind == 'files':
490 raise error.Abort(_('cannot archive plain files to stdout'))
490 raise error.Abort(_('cannot archive plain files to stdout'))
491 dest = cmdutil.makefileobj(ctx, dest)
491 dest = cmdutil.makefileobj(ctx, dest)
492 if not prefix:
492 if not prefix:
493 prefix = os.path.basename(repo.root) + '-%h'
493 prefix = os.path.basename(repo.root) + '-%h'
494
494
495 prefix = cmdutil.makefilename(ctx, prefix)
495 prefix = cmdutil.makefilename(ctx, prefix)
496 match = scmutil.match(ctx, [], opts)
496 match = scmutil.match(ctx, [], opts)
497 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
497 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
498 match, prefix, subrepos=opts.get('subrepos'))
498 match, prefix, subrepos=opts.get('subrepos'))
499
499
500 @command('backout',
500 @command('backout',
501 [('', 'merge', None, _('merge with old dirstate parent after backout')),
501 [('', 'merge', None, _('merge with old dirstate parent after backout')),
502 ('', 'commit', None,
502 ('', 'commit', None,
503 _('commit if no conflicts were encountered (DEPRECATED)')),
503 _('commit if no conflicts were encountered (DEPRECATED)')),
504 ('', 'no-commit', None, _('do not commit')),
504 ('', 'no-commit', None, _('do not commit')),
505 ('', 'parent', '',
505 ('', 'parent', '',
506 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
506 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
507 ('r', 'rev', '', _('revision to backout'), _('REV')),
507 ('r', 'rev', '', _('revision to backout'), _('REV')),
508 ('e', 'edit', False, _('invoke editor on commit messages')),
508 ('e', 'edit', False, _('invoke editor on commit messages')),
509 ] + mergetoolopts + walkopts + commitopts + commitopts2,
509 ] + mergetoolopts + walkopts + commitopts + commitopts2,
510 _('[OPTION]... [-r] REV'))
510 _('[OPTION]... [-r] REV'))
511 def backout(ui, repo, node=None, rev=None, **opts):
511 def backout(ui, repo, node=None, rev=None, **opts):
512 '''reverse effect of earlier changeset
512 '''reverse effect of earlier changeset
513
513
514 Prepare a new changeset with the effect of REV undone in the
514 Prepare a new changeset with the effect of REV undone in the
515 current working directory. If no conflicts were encountered,
515 current working directory. If no conflicts were encountered,
516 it will be committed immediately.
516 it will be committed immediately.
517
517
518 If REV is the parent of the working directory, then this new changeset
518 If REV is the parent of the working directory, then this new changeset
519 is committed automatically (unless --no-commit is specified).
519 is committed automatically (unless --no-commit is specified).
520
520
521 .. note::
521 .. note::
522
522
523 :hg:`backout` cannot be used to fix either an unwanted or
523 :hg:`backout` cannot be used to fix either an unwanted or
524 incorrect merge.
524 incorrect merge.
525
525
526 .. container:: verbose
526 .. container:: verbose
527
527
528 Examples:
528 Examples:
529
529
530 - Reverse the effect of the parent of the working directory.
530 - Reverse the effect of the parent of the working directory.
531 This backout will be committed immediately::
531 This backout will be committed immediately::
532
532
533 hg backout -r .
533 hg backout -r .
534
534
535 - Reverse the effect of previous bad revision 23::
535 - Reverse the effect of previous bad revision 23::
536
536
537 hg backout -r 23
537 hg backout -r 23
538
538
539 - Reverse the effect of previous bad revision 23 and
539 - Reverse the effect of previous bad revision 23 and
540 leave changes uncommitted::
540 leave changes uncommitted::
541
541
542 hg backout -r 23 --no-commit
542 hg backout -r 23 --no-commit
543 hg commit -m "Backout revision 23"
543 hg commit -m "Backout revision 23"
544
544
545 By default, the pending changeset will have one parent,
545 By default, the pending changeset will have one parent,
546 maintaining a linear history. With --merge, the pending
546 maintaining a linear history. With --merge, the pending
547 changeset will instead have two parents: the old parent of the
547 changeset will instead have two parents: the old parent of the
548 working directory and a new child of REV that simply undoes REV.
548 working directory and a new child of REV that simply undoes REV.
549
549
550 Before version 1.7, the behavior without --merge was equivalent
550 Before version 1.7, the behavior without --merge was equivalent
551 to specifying --merge followed by :hg:`update --clean .` to
551 to specifying --merge followed by :hg:`update --clean .` to
552 cancel the merge and leave the child of REV as a head to be
552 cancel the merge and leave the child of REV as a head to be
553 merged separately.
553 merged separately.
554
554
555 See :hg:`help dates` for a list of formats valid for -d/--date.
555 See :hg:`help dates` for a list of formats valid for -d/--date.
556
556
557 See :hg:`help revert` for a way to restore files to the state
557 See :hg:`help revert` for a way to restore files to the state
558 of another revision.
558 of another revision.
559
559
560 Returns 0 on success, 1 if nothing to backout or there are unresolved
560 Returns 0 on success, 1 if nothing to backout or there are unresolved
561 files.
561 files.
562 '''
562 '''
563 wlock = lock = None
563 wlock = lock = None
564 try:
564 try:
565 wlock = repo.wlock()
565 wlock = repo.wlock()
566 lock = repo.lock()
566 lock = repo.lock()
567 return _dobackout(ui, repo, node, rev, **opts)
567 return _dobackout(ui, repo, node, rev, **opts)
568 finally:
568 finally:
569 release(lock, wlock)
569 release(lock, wlock)
570
570
571 def _dobackout(ui, repo, node=None, rev=None, **opts):
571 def _dobackout(ui, repo, node=None, rev=None, **opts):
572 opts = pycompat.byteskwargs(opts)
572 opts = pycompat.byteskwargs(opts)
573 if opts.get('commit') and opts.get('no_commit'):
573 if opts.get('commit') and opts.get('no_commit'):
574 raise error.Abort(_("cannot use --commit with --no-commit"))
574 raise error.Abort(_("cannot use --commit with --no-commit"))
575 if opts.get('merge') and opts.get('no_commit'):
575 if opts.get('merge') and opts.get('no_commit'):
576 raise error.Abort(_("cannot use --merge with --no-commit"))
576 raise error.Abort(_("cannot use --merge with --no-commit"))
577
577
578 if rev and node:
578 if rev and node:
579 raise error.Abort(_("please specify just one revision"))
579 raise error.Abort(_("please specify just one revision"))
580
580
581 if not rev:
581 if not rev:
582 rev = node
582 rev = node
583
583
584 if not rev:
584 if not rev:
585 raise error.Abort(_("please specify a revision to backout"))
585 raise error.Abort(_("please specify a revision to backout"))
586
586
587 date = opts.get('date')
587 date = opts.get('date')
588 if date:
588 if date:
589 opts['date'] = dateutil.parsedate(date)
589 opts['date'] = dateutil.parsedate(date)
590
590
591 cmdutil.checkunfinished(repo)
591 cmdutil.checkunfinished(repo)
592 cmdutil.bailifchanged(repo)
592 cmdutil.bailifchanged(repo)
593 node = scmutil.revsingle(repo, rev).node()
593 node = scmutil.revsingle(repo, rev).node()
594
594
595 op1, op2 = repo.dirstate.parents()
595 op1, op2 = repo.dirstate.parents()
596 if not repo.changelog.isancestor(node, op1):
596 if not repo.changelog.isancestor(node, op1):
597 raise error.Abort(_('cannot backout change that is not an ancestor'))
597 raise error.Abort(_('cannot backout change that is not an ancestor'))
598
598
599 p1, p2 = repo.changelog.parents(node)
599 p1, p2 = repo.changelog.parents(node)
600 if p1 == nullid:
600 if p1 == nullid:
601 raise error.Abort(_('cannot backout a change with no parents'))
601 raise error.Abort(_('cannot backout a change with no parents'))
602 if p2 != nullid:
602 if p2 != nullid:
603 if not opts.get('parent'):
603 if not opts.get('parent'):
604 raise error.Abort(_('cannot backout a merge changeset'))
604 raise error.Abort(_('cannot backout a merge changeset'))
605 p = repo.lookup(opts['parent'])
605 p = repo.lookup(opts['parent'])
606 if p not in (p1, p2):
606 if p not in (p1, p2):
607 raise error.Abort(_('%s is not a parent of %s') %
607 raise error.Abort(_('%s is not a parent of %s') %
608 (short(p), short(node)))
608 (short(p), short(node)))
609 parent = p
609 parent = p
610 else:
610 else:
611 if opts.get('parent'):
611 if opts.get('parent'):
612 raise error.Abort(_('cannot use --parent on non-merge changeset'))
612 raise error.Abort(_('cannot use --parent on non-merge changeset'))
613 parent = p1
613 parent = p1
614
614
615 # the backout should appear on the same branch
615 # the backout should appear on the same branch
616 branch = repo.dirstate.branch()
616 branch = repo.dirstate.branch()
617 bheads = repo.branchheads(branch)
617 bheads = repo.branchheads(branch)
618 rctx = scmutil.revsingle(repo, hex(parent))
618 rctx = scmutil.revsingle(repo, hex(parent))
619 if not opts.get('merge') and op1 != node:
619 if not opts.get('merge') and op1 != node:
620 dsguard = dirstateguard.dirstateguard(repo, 'backout')
620 dsguard = dirstateguard.dirstateguard(repo, 'backout')
621 try:
621 try:
622 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
622 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
623 'backout')
623 'backout')
624 stats = mergemod.update(repo, parent, True, True, node, False)
624 stats = mergemod.update(repo, parent, True, True, node, False)
625 repo.setparents(op1, op2)
625 repo.setparents(op1, op2)
626 dsguard.close()
626 dsguard.close()
627 hg._showstats(repo, stats)
627 hg._showstats(repo, stats)
628 if stats.unresolvedcount:
628 if stats.unresolvedcount:
629 repo.ui.status(_("use 'hg resolve' to retry unresolved "
629 repo.ui.status(_("use 'hg resolve' to retry unresolved "
630 "file merges\n"))
630 "file merges\n"))
631 return 1
631 return 1
632 finally:
632 finally:
633 ui.setconfig('ui', 'forcemerge', '', '')
633 ui.setconfig('ui', 'forcemerge', '', '')
634 lockmod.release(dsguard)
634 lockmod.release(dsguard)
635 else:
635 else:
636 hg.clean(repo, node, show_stats=False)
636 hg.clean(repo, node, show_stats=False)
637 repo.dirstate.setbranch(branch)
637 repo.dirstate.setbranch(branch)
638 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
638 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
639
639
640 if opts.get('no_commit'):
640 if opts.get('no_commit'):
641 msg = _("changeset %s backed out, "
641 msg = _("changeset %s backed out, "
642 "don't forget to commit.\n")
642 "don't forget to commit.\n")
643 ui.status(msg % short(node))
643 ui.status(msg % short(node))
644 return 0
644 return 0
645
645
646 def commitfunc(ui, repo, message, match, opts):
646 def commitfunc(ui, repo, message, match, opts):
647 editform = 'backout'
647 editform = 'backout'
648 e = cmdutil.getcommiteditor(editform=editform,
648 e = cmdutil.getcommiteditor(editform=editform,
649 **pycompat.strkwargs(opts))
649 **pycompat.strkwargs(opts))
650 if not message:
650 if not message:
651 # we don't translate commit messages
651 # we don't translate commit messages
652 message = "Backed out changeset %s" % short(node)
652 message = "Backed out changeset %s" % short(node)
653 e = cmdutil.getcommiteditor(edit=True, editform=editform)
653 e = cmdutil.getcommiteditor(edit=True, editform=editform)
654 return repo.commit(message, opts.get('user'), opts.get('date'),
654 return repo.commit(message, opts.get('user'), opts.get('date'),
655 match, editor=e)
655 match, editor=e)
656 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
656 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
657 if not newnode:
657 if not newnode:
658 ui.status(_("nothing changed\n"))
658 ui.status(_("nothing changed\n"))
659 return 1
659 return 1
660 cmdutil.commitstatus(repo, newnode, branch, bheads)
660 cmdutil.commitstatus(repo, newnode, branch, bheads)
661
661
662 def nice(node):
662 def nice(node):
663 return '%d:%s' % (repo.changelog.rev(node), short(node))
663 return '%d:%s' % (repo.changelog.rev(node), short(node))
664 ui.status(_('changeset %s backs out changeset %s\n') %
664 ui.status(_('changeset %s backs out changeset %s\n') %
665 (nice(repo.changelog.tip()), nice(node)))
665 (nice(repo.changelog.tip()), nice(node)))
666 if opts.get('merge') and op1 != node:
666 if opts.get('merge') and op1 != node:
667 hg.clean(repo, op1, show_stats=False)
667 hg.clean(repo, op1, show_stats=False)
668 ui.status(_('merging with changeset %s\n')
668 ui.status(_('merging with changeset %s\n')
669 % nice(repo.changelog.tip()))
669 % nice(repo.changelog.tip()))
670 try:
670 try:
671 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
671 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
672 'backout')
672 'backout')
673 return hg.merge(repo, hex(repo.changelog.tip()))
673 return hg.merge(repo, hex(repo.changelog.tip()))
674 finally:
674 finally:
675 ui.setconfig('ui', 'forcemerge', '', '')
675 ui.setconfig('ui', 'forcemerge', '', '')
676 return 0
676 return 0
677
677
678 @command('bisect',
678 @command('bisect',
679 [('r', 'reset', False, _('reset bisect state')),
679 [('r', 'reset', False, _('reset bisect state')),
680 ('g', 'good', False, _('mark changeset good')),
680 ('g', 'good', False, _('mark changeset good')),
681 ('b', 'bad', False, _('mark changeset bad')),
681 ('b', 'bad', False, _('mark changeset bad')),
682 ('s', 'skip', False, _('skip testing changeset')),
682 ('s', 'skip', False, _('skip testing changeset')),
683 ('e', 'extend', False, _('extend the bisect range')),
683 ('e', 'extend', False, _('extend the bisect range')),
684 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
684 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
685 ('U', 'noupdate', False, _('do not update to target'))],
685 ('U', 'noupdate', False, _('do not update to target'))],
686 _("[-gbsr] [-U] [-c CMD] [REV]"))
686 _("[-gbsr] [-U] [-c CMD] [REV]"))
687 def bisect(ui, repo, rev=None, extra=None, command=None,
687 def bisect(ui, repo, rev=None, extra=None, command=None,
688 reset=None, good=None, bad=None, skip=None, extend=None,
688 reset=None, good=None, bad=None, skip=None, extend=None,
689 noupdate=None):
689 noupdate=None):
690 """subdivision search of changesets
690 """subdivision search of changesets
691
691
692 This command helps to find changesets which introduce problems. To
692 This command helps to find changesets which introduce problems. To
693 use, mark the earliest changeset you know exhibits the problem as
693 use, mark the earliest changeset you know exhibits the problem as
694 bad, then mark the latest changeset which is free from the problem
694 bad, then mark the latest changeset which is free from the problem
695 as good. Bisect will update your working directory to a revision
695 as good. Bisect will update your working directory to a revision
696 for testing (unless the -U/--noupdate option is specified). Once
696 for testing (unless the -U/--noupdate option is specified). Once
697 you have performed tests, mark the working directory as good or
697 you have performed tests, mark the working directory as good or
698 bad, and bisect will either update to another candidate changeset
698 bad, and bisect will either update to another candidate changeset
699 or announce that it has found the bad revision.
699 or announce that it has found the bad revision.
700
700
701 As a shortcut, you can also use the revision argument to mark a
701 As a shortcut, you can also use the revision argument to mark a
702 revision as good or bad without checking it out first.
702 revision as good or bad without checking it out first.
703
703
704 If you supply a command, it will be used for automatic bisection.
704 If you supply a command, it will be used for automatic bisection.
705 The environment variable HG_NODE will contain the ID of the
705 The environment variable HG_NODE will contain the ID of the
706 changeset being tested. The exit status of the command will be
706 changeset being tested. The exit status of the command will be
707 used to mark revisions as good or bad: status 0 means good, 125
707 used to mark revisions as good or bad: status 0 means good, 125
708 means to skip the revision, 127 (command not found) will abort the
708 means to skip the revision, 127 (command not found) will abort the
709 bisection, and any other non-zero exit status means the revision
709 bisection, and any other non-zero exit status means the revision
710 is bad.
710 is bad.
711
711
712 .. container:: verbose
712 .. container:: verbose
713
713
714 Some examples:
714 Some examples:
715
715
716 - start a bisection with known bad revision 34, and good revision 12::
716 - start a bisection with known bad revision 34, and good revision 12::
717
717
718 hg bisect --bad 34
718 hg bisect --bad 34
719 hg bisect --good 12
719 hg bisect --good 12
720
720
721 - advance the current bisection by marking current revision as good or
721 - advance the current bisection by marking current revision as good or
722 bad::
722 bad::
723
723
724 hg bisect --good
724 hg bisect --good
725 hg bisect --bad
725 hg bisect --bad
726
726
727 - mark the current revision, or a known revision, to be skipped (e.g. if
727 - mark the current revision, or a known revision, to be skipped (e.g. if
728 that revision is not usable because of another issue)::
728 that revision is not usable because of another issue)::
729
729
730 hg bisect --skip
730 hg bisect --skip
731 hg bisect --skip 23
731 hg bisect --skip 23
732
732
733 - skip all revisions that do not touch directories ``foo`` or ``bar``::
733 - skip all revisions that do not touch directories ``foo`` or ``bar``::
734
734
735 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
735 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
736
736
737 - forget the current bisection::
737 - forget the current bisection::
738
738
739 hg bisect --reset
739 hg bisect --reset
740
740
741 - use 'make && make tests' to automatically find the first broken
741 - use 'make && make tests' to automatically find the first broken
742 revision::
742 revision::
743
743
744 hg bisect --reset
744 hg bisect --reset
745 hg bisect --bad 34
745 hg bisect --bad 34
746 hg bisect --good 12
746 hg bisect --good 12
747 hg bisect --command "make && make tests"
747 hg bisect --command "make && make tests"
748
748
749 - see all changesets whose states are already known in the current
749 - see all changesets whose states are already known in the current
750 bisection::
750 bisection::
751
751
752 hg log -r "bisect(pruned)"
752 hg log -r "bisect(pruned)"
753
753
754 - see the changeset currently being bisected (especially useful
754 - see the changeset currently being bisected (especially useful
755 if running with -U/--noupdate)::
755 if running with -U/--noupdate)::
756
756
757 hg log -r "bisect(current)"
757 hg log -r "bisect(current)"
758
758
759 - see all changesets that took part in the current bisection::
759 - see all changesets that took part in the current bisection::
760
760
761 hg log -r "bisect(range)"
761 hg log -r "bisect(range)"
762
762
763 - you can even get a nice graph::
763 - you can even get a nice graph::
764
764
765 hg log --graph -r "bisect(range)"
765 hg log --graph -r "bisect(range)"
766
766
767 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
767 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
768
768
769 Returns 0 on success.
769 Returns 0 on success.
770 """
770 """
771 # backward compatibility
771 # backward compatibility
772 if rev in "good bad reset init".split():
772 if rev in "good bad reset init".split():
773 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
773 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
774 cmd, rev, extra = rev, extra, None
774 cmd, rev, extra = rev, extra, None
775 if cmd == "good":
775 if cmd == "good":
776 good = True
776 good = True
777 elif cmd == "bad":
777 elif cmd == "bad":
778 bad = True
778 bad = True
779 else:
779 else:
780 reset = True
780 reset = True
781 elif extra:
781 elif extra:
782 raise error.Abort(_('incompatible arguments'))
782 raise error.Abort(_('incompatible arguments'))
783
783
784 incompatibles = {
784 incompatibles = {
785 '--bad': bad,
785 '--bad': bad,
786 '--command': bool(command),
786 '--command': bool(command),
787 '--extend': extend,
787 '--extend': extend,
788 '--good': good,
788 '--good': good,
789 '--reset': reset,
789 '--reset': reset,
790 '--skip': skip,
790 '--skip': skip,
791 }
791 }
792
792
793 enabled = [x for x in incompatibles if incompatibles[x]]
793 enabled = [x for x in incompatibles if incompatibles[x]]
794
794
795 if len(enabled) > 1:
795 if len(enabled) > 1:
796 raise error.Abort(_('%s and %s are incompatible') %
796 raise error.Abort(_('%s and %s are incompatible') %
797 tuple(sorted(enabled)[0:2]))
797 tuple(sorted(enabled)[0:2]))
798
798
799 if reset:
799 if reset:
800 hbisect.resetstate(repo)
800 hbisect.resetstate(repo)
801 return
801 return
802
802
803 state = hbisect.load_state(repo)
803 state = hbisect.load_state(repo)
804
804
805 # update state
805 # update state
806 if good or bad or skip:
806 if good or bad or skip:
807 if rev:
807 if rev:
808 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
808 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
809 else:
809 else:
810 nodes = [repo.lookup('.')]
810 nodes = [repo.lookup('.')]
811 if good:
811 if good:
812 state['good'] += nodes
812 state['good'] += nodes
813 elif bad:
813 elif bad:
814 state['bad'] += nodes
814 state['bad'] += nodes
815 elif skip:
815 elif skip:
816 state['skip'] += nodes
816 state['skip'] += nodes
817 hbisect.save_state(repo, state)
817 hbisect.save_state(repo, state)
818 if not (state['good'] and state['bad']):
818 if not (state['good'] and state['bad']):
819 return
819 return
820
820
821 def mayupdate(repo, node, show_stats=True):
821 def mayupdate(repo, node, show_stats=True):
822 """common used update sequence"""
822 """common used update sequence"""
823 if noupdate:
823 if noupdate:
824 return
824 return
825 cmdutil.checkunfinished(repo)
825 cmdutil.checkunfinished(repo)
826 cmdutil.bailifchanged(repo)
826 cmdutil.bailifchanged(repo)
827 return hg.clean(repo, node, show_stats=show_stats)
827 return hg.clean(repo, node, show_stats=show_stats)
828
828
829 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
829 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
830
830
831 if command:
831 if command:
832 changesets = 1
832 changesets = 1
833 if noupdate:
833 if noupdate:
834 try:
834 try:
835 node = state['current'][0]
835 node = state['current'][0]
836 except LookupError:
836 except LookupError:
837 raise error.Abort(_('current bisect revision is unknown - '
837 raise error.Abort(_('current bisect revision is unknown - '
838 'start a new bisect to fix'))
838 'start a new bisect to fix'))
839 else:
839 else:
840 node, p2 = repo.dirstate.parents()
840 node, p2 = repo.dirstate.parents()
841 if p2 != nullid:
841 if p2 != nullid:
842 raise error.Abort(_('current bisect revision is a merge'))
842 raise error.Abort(_('current bisect revision is a merge'))
843 if rev:
843 if rev:
844 node = repo[scmutil.revsingle(repo, rev, node)].node()
844 node = repo[scmutil.revsingle(repo, rev, node)].node()
845 try:
845 try:
846 while changesets:
846 while changesets:
847 # update state
847 # update state
848 state['current'] = [node]
848 state['current'] = [node]
849 hbisect.save_state(repo, state)
849 hbisect.save_state(repo, state)
850 status = ui.system(command, environ={'HG_NODE': hex(node)},
850 status = ui.system(command, environ={'HG_NODE': hex(node)},
851 blockedtag='bisect_check')
851 blockedtag='bisect_check')
852 if status == 125:
852 if status == 125:
853 transition = "skip"
853 transition = "skip"
854 elif status == 0:
854 elif status == 0:
855 transition = "good"
855 transition = "good"
856 # status < 0 means process was killed
856 # status < 0 means process was killed
857 elif status == 127:
857 elif status == 127:
858 raise error.Abort(_("failed to execute %s") % command)
858 raise error.Abort(_("failed to execute %s") % command)
859 elif status < 0:
859 elif status < 0:
860 raise error.Abort(_("%s killed") % command)
860 raise error.Abort(_("%s killed") % command)
861 else:
861 else:
862 transition = "bad"
862 transition = "bad"
863 state[transition].append(node)
863 state[transition].append(node)
864 ctx = repo[node]
864 ctx = repo[node]
865 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
865 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
866 transition))
866 transition))
867 hbisect.checkstate(state)
867 hbisect.checkstate(state)
868 # bisect
868 # bisect
869 nodes, changesets, bgood = hbisect.bisect(repo, state)
869 nodes, changesets, bgood = hbisect.bisect(repo, state)
870 # update to next check
870 # update to next check
871 node = nodes[0]
871 node = nodes[0]
872 mayupdate(repo, node, show_stats=False)
872 mayupdate(repo, node, show_stats=False)
873 finally:
873 finally:
874 state['current'] = [node]
874 state['current'] = [node]
875 hbisect.save_state(repo, state)
875 hbisect.save_state(repo, state)
876 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
876 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
877 return
877 return
878
878
879 hbisect.checkstate(state)
879 hbisect.checkstate(state)
880
880
881 # actually bisect
881 # actually bisect
882 nodes, changesets, good = hbisect.bisect(repo, state)
882 nodes, changesets, good = hbisect.bisect(repo, state)
883 if extend:
883 if extend:
884 if not changesets:
884 if not changesets:
885 extendnode = hbisect.extendrange(repo, state, nodes, good)
885 extendnode = hbisect.extendrange(repo, state, nodes, good)
886 if extendnode is not None:
886 if extendnode is not None:
887 ui.write(_("Extending search to changeset %d:%s\n")
887 ui.write(_("Extending search to changeset %d:%s\n")
888 % (extendnode.rev(), extendnode))
888 % (extendnode.rev(), extendnode))
889 state['current'] = [extendnode.node()]
889 state['current'] = [extendnode.node()]
890 hbisect.save_state(repo, state)
890 hbisect.save_state(repo, state)
891 return mayupdate(repo, extendnode.node())
891 return mayupdate(repo, extendnode.node())
892 raise error.Abort(_("nothing to extend"))
892 raise error.Abort(_("nothing to extend"))
893
893
894 if changesets == 0:
894 if changesets == 0:
895 hbisect.printresult(ui, repo, state, displayer, nodes, good)
895 hbisect.printresult(ui, repo, state, displayer, nodes, good)
896 else:
896 else:
897 assert len(nodes) == 1 # only a single node can be tested next
897 assert len(nodes) == 1 # only a single node can be tested next
898 node = nodes[0]
898 node = nodes[0]
899 # compute the approximate number of remaining tests
899 # compute the approximate number of remaining tests
900 tests, size = 0, 2
900 tests, size = 0, 2
901 while size <= changesets:
901 while size <= changesets:
902 tests, size = tests + 1, size * 2
902 tests, size = tests + 1, size * 2
903 rev = repo.changelog.rev(node)
903 rev = repo.changelog.rev(node)
904 ui.write(_("Testing changeset %d:%s "
904 ui.write(_("Testing changeset %d:%s "
905 "(%d changesets remaining, ~%d tests)\n")
905 "(%d changesets remaining, ~%d tests)\n")
906 % (rev, short(node), changesets, tests))
906 % (rev, short(node), changesets, tests))
907 state['current'] = [node]
907 state['current'] = [node]
908 hbisect.save_state(repo, state)
908 hbisect.save_state(repo, state)
909 return mayupdate(repo, node)
909 return mayupdate(repo, node)
910
910
911 @command('bookmarks|bookmark',
911 @command('bookmarks|bookmark',
912 [('f', 'force', False, _('force')),
912 [('f', 'force', False, _('force')),
913 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
913 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
914 ('d', 'delete', False, _('delete a given bookmark')),
914 ('d', 'delete', False, _('delete a given bookmark')),
915 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
915 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
916 ('i', 'inactive', False, _('mark a bookmark inactive')),
916 ('i', 'inactive', False, _('mark a bookmark inactive')),
917 ] + formatteropts,
917 ] + formatteropts,
918 _('hg bookmarks [OPTIONS]... [NAME]...'))
918 _('hg bookmarks [OPTIONS]... [NAME]...'))
919 def bookmark(ui, repo, *names, **opts):
919 def bookmark(ui, repo, *names, **opts):
920 '''create a new bookmark or list existing bookmarks
920 '''create a new bookmark or list existing bookmarks
921
921
922 Bookmarks are labels on changesets to help track lines of development.
922 Bookmarks are labels on changesets to help track lines of development.
923 Bookmarks are unversioned and can be moved, renamed and deleted.
923 Bookmarks are unversioned and can be moved, renamed and deleted.
924 Deleting or moving a bookmark has no effect on the associated changesets.
924 Deleting or moving a bookmark has no effect on the associated changesets.
925
925
926 Creating or updating to a bookmark causes it to be marked as 'active'.
926 Creating or updating to a bookmark causes it to be marked as 'active'.
927 The active bookmark is indicated with a '*'.
927 The active bookmark is indicated with a '*'.
928 When a commit is made, the active bookmark will advance to the new commit.
928 When a commit is made, the active bookmark will advance to the new commit.
929 A plain :hg:`update` will also advance an active bookmark, if possible.
929 A plain :hg:`update` will also advance an active bookmark, if possible.
930 Updating away from a bookmark will cause it to be deactivated.
930 Updating away from a bookmark will cause it to be deactivated.
931
931
932 Bookmarks can be pushed and pulled between repositories (see
932 Bookmarks can be pushed and pulled between repositories (see
933 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
933 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
934 diverged, a new 'divergent bookmark' of the form 'name@path' will
934 diverged, a new 'divergent bookmark' of the form 'name@path' will
935 be created. Using :hg:`merge` will resolve the divergence.
935 be created. Using :hg:`merge` will resolve the divergence.
936
936
937 Specifying bookmark as '.' to -m or -d options is equivalent to specifying
937 Specifying bookmark as '.' to -m or -d options is equivalent to specifying
938 the active bookmark's name.
938 the active bookmark's name.
939
939
940 A bookmark named '@' has the special property that :hg:`clone` will
940 A bookmark named '@' has the special property that :hg:`clone` will
941 check it out by default if it exists.
941 check it out by default if it exists.
942
942
943 .. container:: verbose
943 .. container:: verbose
944
944
945 Examples:
945 Examples:
946
946
947 - create an active bookmark for a new line of development::
947 - create an active bookmark for a new line of development::
948
948
949 hg book new-feature
949 hg book new-feature
950
950
951 - create an inactive bookmark as a place marker::
951 - create an inactive bookmark as a place marker::
952
952
953 hg book -i reviewed
953 hg book -i reviewed
954
954
955 - create an inactive bookmark on another changeset::
955 - create an inactive bookmark on another changeset::
956
956
957 hg book -r .^ tested
957 hg book -r .^ tested
958
958
959 - rename bookmark turkey to dinner::
959 - rename bookmark turkey to dinner::
960
960
961 hg book -m turkey dinner
961 hg book -m turkey dinner
962
962
963 - move the '@' bookmark from another branch::
963 - move the '@' bookmark from another branch::
964
964
965 hg book -f @
965 hg book -f @
966 '''
966 '''
967 force = opts.get(r'force')
967 force = opts.get(r'force')
968 rev = opts.get(r'rev')
968 rev = opts.get(r'rev')
969 delete = opts.get(r'delete')
969 delete = opts.get(r'delete')
970 rename = opts.get(r'rename')
970 rename = opts.get(r'rename')
971 inactive = opts.get(r'inactive')
971 inactive = opts.get(r'inactive')
972
972
973 if delete and rename:
973 if delete and rename:
974 raise error.Abort(_("--delete and --rename are incompatible"))
974 raise error.Abort(_("--delete and --rename are incompatible"))
975 if delete and rev:
975 if delete and rev:
976 raise error.Abort(_("--rev is incompatible with --delete"))
976 raise error.Abort(_("--rev is incompatible with --delete"))
977 if rename and rev:
977 if rename and rev:
978 raise error.Abort(_("--rev is incompatible with --rename"))
978 raise error.Abort(_("--rev is incompatible with --rename"))
979 if not names and (delete or rev):
979 if not names and (delete or rev):
980 raise error.Abort(_("bookmark name required"))
980 raise error.Abort(_("bookmark name required"))
981
981
982 if delete or rename or names or inactive:
982 if delete or rename or names or inactive:
983 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
983 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
984 if delete:
984 if delete:
985 names = pycompat.maplist(repo._bookmarks.expandname, names)
985 names = pycompat.maplist(repo._bookmarks.expandname, names)
986 bookmarks.delete(repo, tr, names)
986 bookmarks.delete(repo, tr, names)
987 elif rename:
987 elif rename:
988 if not names:
988 if not names:
989 raise error.Abort(_("new bookmark name required"))
989 raise error.Abort(_("new bookmark name required"))
990 elif len(names) > 1:
990 elif len(names) > 1:
991 raise error.Abort(_("only one new bookmark name allowed"))
991 raise error.Abort(_("only one new bookmark name allowed"))
992 rename = repo._bookmarks.expandname(rename)
992 rename = repo._bookmarks.expandname(rename)
993 bookmarks.rename(repo, tr, rename, names[0], force, inactive)
993 bookmarks.rename(repo, tr, rename, names[0], force, inactive)
994 elif names:
994 elif names:
995 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
995 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
996 elif inactive:
996 elif inactive:
997 if len(repo._bookmarks) == 0:
997 if len(repo._bookmarks) == 0:
998 ui.status(_("no bookmarks set\n"))
998 ui.status(_("no bookmarks set\n"))
999 elif not repo._activebookmark:
999 elif not repo._activebookmark:
1000 ui.status(_("no active bookmark\n"))
1000 ui.status(_("no active bookmark\n"))
1001 else:
1001 else:
1002 bookmarks.deactivate(repo)
1002 bookmarks.deactivate(repo)
1003 else: # show bookmarks
1003 else: # show bookmarks
1004 bookmarks.printbookmarks(ui, repo, **opts)
1004 bookmarks.printbookmarks(ui, repo, **opts)
1005
1005
1006 @command('branch',
1006 @command('branch',
1007 [('f', 'force', None,
1007 [('f', 'force', None,
1008 _('set branch name even if it shadows an existing branch')),
1008 _('set branch name even if it shadows an existing branch')),
1009 ('C', 'clean', None, _('reset branch name to parent branch name')),
1009 ('C', 'clean', None, _('reset branch name to parent branch name')),
1010 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1010 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1011 ],
1011 ],
1012 _('[-fC] [NAME]'))
1012 _('[-fC] [NAME]'))
1013 def branch(ui, repo, label=None, **opts):
1013 def branch(ui, repo, label=None, **opts):
1014 """set or show the current branch name
1014 """set or show the current branch name
1015
1015
1016 .. note::
1016 .. note::
1017
1017
1018 Branch names are permanent and global. Use :hg:`bookmark` to create a
1018 Branch names are permanent and global. Use :hg:`bookmark` to create a
1019 light-weight bookmark instead. See :hg:`help glossary` for more
1019 light-weight bookmark instead. See :hg:`help glossary` for more
1020 information about named branches and bookmarks.
1020 information about named branches and bookmarks.
1021
1021
1022 With no argument, show the current branch name. With one argument,
1022 With no argument, show the current branch name. With one argument,
1023 set the working directory branch name (the branch will not exist
1023 set the working directory branch name (the branch will not exist
1024 in the repository until the next commit). Standard practice
1024 in the repository until the next commit). Standard practice
1025 recommends that primary development take place on the 'default'
1025 recommends that primary development take place on the 'default'
1026 branch.
1026 branch.
1027
1027
1028 Unless -f/--force is specified, branch will not let you set a
1028 Unless -f/--force is specified, branch will not let you set a
1029 branch name that already exists.
1029 branch name that already exists.
1030
1030
1031 Use -C/--clean to reset the working directory branch to that of
1031 Use -C/--clean to reset the working directory branch to that of
1032 the parent of the working directory, negating a previous branch
1032 the parent of the working directory, negating a previous branch
1033 change.
1033 change.
1034
1034
1035 Use the command :hg:`update` to switch to an existing branch. Use
1035 Use the command :hg:`update` to switch to an existing branch. Use
1036 :hg:`commit --close-branch` to mark this branch head as closed.
1036 :hg:`commit --close-branch` to mark this branch head as closed.
1037 When all heads of a branch are closed, the branch will be
1037 When all heads of a branch are closed, the branch will be
1038 considered closed.
1038 considered closed.
1039
1039
1040 Returns 0 on success.
1040 Returns 0 on success.
1041 """
1041 """
1042 opts = pycompat.byteskwargs(opts)
1042 opts = pycompat.byteskwargs(opts)
1043 revs = opts.get('rev')
1043 revs = opts.get('rev')
1044 if label:
1044 if label:
1045 label = label.strip()
1045 label = label.strip()
1046
1046
1047 if not opts.get('clean') and not label:
1047 if not opts.get('clean') and not label:
1048 if revs:
1048 if revs:
1049 raise error.Abort(_("no branch name specified for the revisions"))
1049 raise error.Abort(_("no branch name specified for the revisions"))
1050 ui.write("%s\n" % repo.dirstate.branch())
1050 ui.write("%s\n" % repo.dirstate.branch())
1051 return
1051 return
1052
1052
1053 with repo.wlock():
1053 with repo.wlock():
1054 if opts.get('clean'):
1054 if opts.get('clean'):
1055 label = repo[None].p1().branch()
1055 label = repo[None].p1().branch()
1056 repo.dirstate.setbranch(label)
1056 repo.dirstate.setbranch(label)
1057 ui.status(_('reset working directory to branch %s\n') % label)
1057 ui.status(_('reset working directory to branch %s\n') % label)
1058 elif label:
1058 elif label:
1059
1059
1060 scmutil.checknewlabel(repo, label, 'branch')
1060 scmutil.checknewlabel(repo, label, 'branch')
1061 if revs:
1061 if revs:
1062 return cmdutil.changebranch(ui, repo, revs, label)
1062 return cmdutil.changebranch(ui, repo, revs, label)
1063
1063
1064 if not opts.get('force') and label in repo.branchmap():
1064 if not opts.get('force') and label in repo.branchmap():
1065 if label not in [p.branch() for p in repo[None].parents()]:
1065 if label not in [p.branch() for p in repo[None].parents()]:
1066 raise error.Abort(_('a branch of the same name already'
1066 raise error.Abort(_('a branch of the same name already'
1067 ' exists'),
1067 ' exists'),
1068 # i18n: "it" refers to an existing branch
1068 # i18n: "it" refers to an existing branch
1069 hint=_("use 'hg update' to switch to it"))
1069 hint=_("use 'hg update' to switch to it"))
1070
1070
1071 repo.dirstate.setbranch(label)
1071 repo.dirstate.setbranch(label)
1072 ui.status(_('marked working directory as branch %s\n') % label)
1072 ui.status(_('marked working directory as branch %s\n') % label)
1073
1073
1074 # find any open named branches aside from default
1074 # find any open named branches aside from default
1075 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1075 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1076 if n != "default" and not c]
1076 if n != "default" and not c]
1077 if not others:
1077 if not others:
1078 ui.status(_('(branches are permanent and global, '
1078 ui.status(_('(branches are permanent and global, '
1079 'did you want a bookmark?)\n'))
1079 'did you want a bookmark?)\n'))
1080
1080
1081 @command('branches',
1081 @command('branches',
1082 [('a', 'active', False,
1082 [('a', 'active', False,
1083 _('show only branches that have unmerged heads (DEPRECATED)')),
1083 _('show only branches that have unmerged heads (DEPRECATED)')),
1084 ('c', 'closed', False, _('show normal and closed branches')),
1084 ('c', 'closed', False, _('show normal and closed branches')),
1085 ] + formatteropts,
1085 ] + formatteropts,
1086 _('[-c]'), cmdtype=readonly)
1086 _('[-c]'), cmdtype=readonly)
1087 def branches(ui, repo, active=False, closed=False, **opts):
1087 def branches(ui, repo, active=False, closed=False, **opts):
1088 """list repository named branches
1088 """list repository named branches
1089
1089
1090 List the repository's named branches, indicating which ones are
1090 List the repository's named branches, indicating which ones are
1091 inactive. If -c/--closed is specified, also list branches which have
1091 inactive. If -c/--closed is specified, also list branches which have
1092 been marked closed (see :hg:`commit --close-branch`).
1092 been marked closed (see :hg:`commit --close-branch`).
1093
1093
1094 Use the command :hg:`update` to switch to an existing branch.
1094 Use the command :hg:`update` to switch to an existing branch.
1095
1095
1096 Returns 0.
1096 Returns 0.
1097 """
1097 """
1098
1098
1099 opts = pycompat.byteskwargs(opts)
1099 opts = pycompat.byteskwargs(opts)
1100 ui.pager('branches')
1100 ui.pager('branches')
1101 fm = ui.formatter('branches', opts)
1101 fm = ui.formatter('branches', opts)
1102 hexfunc = fm.hexfunc
1102 hexfunc = fm.hexfunc
1103
1103
1104 allheads = set(repo.heads())
1104 allheads = set(repo.heads())
1105 branches = []
1105 branches = []
1106 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1106 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1107 isactive = False
1107 isactive = False
1108 if not isclosed:
1108 if not isclosed:
1109 openheads = set(repo.branchmap().iteropen(heads))
1109 openheads = set(repo.branchmap().iteropen(heads))
1110 isactive = bool(openheads & allheads)
1110 isactive = bool(openheads & allheads)
1111 branches.append((tag, repo[tip], isactive, not isclosed))
1111 branches.append((tag, repo[tip], isactive, not isclosed))
1112 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1112 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1113 reverse=True)
1113 reverse=True)
1114
1114
1115 for tag, ctx, isactive, isopen in branches:
1115 for tag, ctx, isactive, isopen in branches:
1116 if active and not isactive:
1116 if active and not isactive:
1117 continue
1117 continue
1118 if isactive:
1118 if isactive:
1119 label = 'branches.active'
1119 label = 'branches.active'
1120 notice = ''
1120 notice = ''
1121 elif not isopen:
1121 elif not isopen:
1122 if not closed:
1122 if not closed:
1123 continue
1123 continue
1124 label = 'branches.closed'
1124 label = 'branches.closed'
1125 notice = _(' (closed)')
1125 notice = _(' (closed)')
1126 else:
1126 else:
1127 label = 'branches.inactive'
1127 label = 'branches.inactive'
1128 notice = _(' (inactive)')
1128 notice = _(' (inactive)')
1129 current = (tag == repo.dirstate.branch())
1129 current = (tag == repo.dirstate.branch())
1130 if current:
1130 if current:
1131 label = 'branches.current'
1131 label = 'branches.current'
1132
1132
1133 fm.startitem()
1133 fm.startitem()
1134 fm.write('branch', '%s', tag, label=label)
1134 fm.write('branch', '%s', tag, label=label)
1135 rev = ctx.rev()
1135 rev = ctx.rev()
1136 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1136 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1137 fmt = ' ' * padsize + ' %d:%s'
1137 fmt = ' ' * padsize + ' %d:%s'
1138 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1138 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1139 label='log.changeset changeset.%s' % ctx.phasestr())
1139 label='log.changeset changeset.%s' % ctx.phasestr())
1140 fm.context(ctx=ctx)
1140 fm.context(ctx=ctx)
1141 fm.data(active=isactive, closed=not isopen, current=current)
1141 fm.data(active=isactive, closed=not isopen, current=current)
1142 if not ui.quiet:
1142 if not ui.quiet:
1143 fm.plain(notice)
1143 fm.plain(notice)
1144 fm.plain('\n')
1144 fm.plain('\n')
1145 fm.end()
1145 fm.end()
1146
1146
1147 @command('bundle',
1147 @command('bundle',
1148 [('f', 'force', None, _('run even when the destination is unrelated')),
1148 [('f', 'force', None, _('run even when the destination is unrelated')),
1149 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1149 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1150 _('REV')),
1150 _('REV')),
1151 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1151 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1152 _('BRANCH')),
1152 _('BRANCH')),
1153 ('', 'base', [],
1153 ('', 'base', [],
1154 _('a base changeset assumed to be available at the destination'),
1154 _('a base changeset assumed to be available at the destination'),
1155 _('REV')),
1155 _('REV')),
1156 ('a', 'all', None, _('bundle all changesets in the repository')),
1156 ('a', 'all', None, _('bundle all changesets in the repository')),
1157 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1157 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1158 ] + remoteopts,
1158 ] + remoteopts,
1159 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1159 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1160 def bundle(ui, repo, fname, dest=None, **opts):
1160 def bundle(ui, repo, fname, dest=None, **opts):
1161 """create a bundle file
1161 """create a bundle file
1162
1162
1163 Generate a bundle file containing data to be transferred to another
1163 Generate a bundle file containing data to be transferred to another
1164 repository.
1164 repository.
1165
1165
1166 To create a bundle containing all changesets, use -a/--all
1166 To create a bundle containing all changesets, use -a/--all
1167 (or --base null). Otherwise, hg assumes the destination will have
1167 (or --base null). Otherwise, hg assumes the destination will have
1168 all the nodes you specify with --base parameters. Otherwise, hg
1168 all the nodes you specify with --base parameters. Otherwise, hg
1169 will assume the repository has all the nodes in destination, or
1169 will assume the repository has all the nodes in destination, or
1170 default-push/default if no destination is specified, where destination
1170 default-push/default if no destination is specified, where destination
1171 is the repository you provide through DEST option.
1171 is the repository you provide through DEST option.
1172
1172
1173 You can change bundle format with the -t/--type option. See
1173 You can change bundle format with the -t/--type option. See
1174 :hg:`help bundlespec` for documentation on this format. By default,
1174 :hg:`help bundlespec` for documentation on this format. By default,
1175 the most appropriate format is used and compression defaults to
1175 the most appropriate format is used and compression defaults to
1176 bzip2.
1176 bzip2.
1177
1177
1178 The bundle file can then be transferred using conventional means
1178 The bundle file can then be transferred using conventional means
1179 and applied to another repository with the unbundle or pull
1179 and applied to another repository with the unbundle or pull
1180 command. This is useful when direct push and pull are not
1180 command. This is useful when direct push and pull are not
1181 available or when exporting an entire repository is undesirable.
1181 available or when exporting an entire repository is undesirable.
1182
1182
1183 Applying bundles preserves all changeset contents including
1183 Applying bundles preserves all changeset contents including
1184 permissions, copy/rename information, and revision history.
1184 permissions, copy/rename information, and revision history.
1185
1185
1186 Returns 0 on success, 1 if no changes found.
1186 Returns 0 on success, 1 if no changes found.
1187 """
1187 """
1188 opts = pycompat.byteskwargs(opts)
1188 opts = pycompat.byteskwargs(opts)
1189 revs = None
1189 revs = None
1190 if 'rev' in opts:
1190 if 'rev' in opts:
1191 revstrings = opts['rev']
1191 revstrings = opts['rev']
1192 revs = scmutil.revrange(repo, revstrings)
1192 revs = scmutil.revrange(repo, revstrings)
1193 if revstrings and not revs:
1193 if revstrings and not revs:
1194 raise error.Abort(_('no commits to bundle'))
1194 raise error.Abort(_('no commits to bundle'))
1195
1195
1196 bundletype = opts.get('type', 'bzip2').lower()
1196 bundletype = opts.get('type', 'bzip2').lower()
1197 try:
1197 try:
1198 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1198 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1199 except error.UnsupportedBundleSpecification as e:
1199 except error.UnsupportedBundleSpecification as e:
1200 raise error.Abort(pycompat.bytestr(e),
1200 raise error.Abort(pycompat.bytestr(e),
1201 hint=_("see 'hg help bundlespec' for supported "
1201 hint=_("see 'hg help bundlespec' for supported "
1202 "values for --type"))
1202 "values for --type"))
1203 cgversion = bundlespec.contentopts["cg.version"]
1203 cgversion = bundlespec.contentopts["cg.version"]
1204
1204
1205 # Packed bundles are a pseudo bundle format for now.
1205 # Packed bundles are a pseudo bundle format for now.
1206 if cgversion == 's1':
1206 if cgversion == 's1':
1207 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1207 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1208 hint=_("use 'hg debugcreatestreamclonebundle'"))
1208 hint=_("use 'hg debugcreatestreamclonebundle'"))
1209
1209
1210 if opts.get('all'):
1210 if opts.get('all'):
1211 if dest:
1211 if dest:
1212 raise error.Abort(_("--all is incompatible with specifying "
1212 raise error.Abort(_("--all is incompatible with specifying "
1213 "a destination"))
1213 "a destination"))
1214 if opts.get('base'):
1214 if opts.get('base'):
1215 ui.warn(_("ignoring --base because --all was specified\n"))
1215 ui.warn(_("ignoring --base because --all was specified\n"))
1216 base = ['null']
1216 base = ['null']
1217 else:
1217 else:
1218 base = scmutil.revrange(repo, opts.get('base'))
1218 base = scmutil.revrange(repo, opts.get('base'))
1219 if cgversion not in changegroup.supportedoutgoingversions(repo):
1219 if cgversion not in changegroup.supportedoutgoingversions(repo):
1220 raise error.Abort(_("repository does not support bundle version %s") %
1220 raise error.Abort(_("repository does not support bundle version %s") %
1221 cgversion)
1221 cgversion)
1222
1222
1223 if base:
1223 if base:
1224 if dest:
1224 if dest:
1225 raise error.Abort(_("--base is incompatible with specifying "
1225 raise error.Abort(_("--base is incompatible with specifying "
1226 "a destination"))
1226 "a destination"))
1227 common = [repo[rev].node() for rev in base]
1227 common = [repo[rev].node() for rev in base]
1228 heads = [repo[r].node() for r in revs] if revs else None
1228 heads = [repo[r].node() for r in revs] if revs else None
1229 outgoing = discovery.outgoing(repo, common, heads)
1229 outgoing = discovery.outgoing(repo, common, heads)
1230 else:
1230 else:
1231 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1231 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1232 dest, branches = hg.parseurl(dest, opts.get('branch'))
1232 dest, branches = hg.parseurl(dest, opts.get('branch'))
1233 other = hg.peer(repo, opts, dest)
1233 other = hg.peer(repo, opts, dest)
1234 revs = [repo[r].hex() for r in revs]
1234 revs = [repo[r].hex() for r in revs]
1235 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1235 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1236 heads = revs and map(repo.lookup, revs) or revs
1236 heads = revs and map(repo.lookup, revs) or revs
1237 outgoing = discovery.findcommonoutgoing(repo, other,
1237 outgoing = discovery.findcommonoutgoing(repo, other,
1238 onlyheads=heads,
1238 onlyheads=heads,
1239 force=opts.get('force'),
1239 force=opts.get('force'),
1240 portable=True)
1240 portable=True)
1241
1241
1242 if not outgoing.missing:
1242 if not outgoing.missing:
1243 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1243 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1244 return 1
1244 return 1
1245
1245
1246 bcompression = bundlespec.compression
1246 bcompression = bundlespec.compression
1247 if cgversion == '01': #bundle1
1247 if cgversion == '01': #bundle1
1248 if bcompression is None:
1248 if bcompression is None:
1249 bcompression = 'UN'
1249 bcompression = 'UN'
1250 bversion = 'HG10' + bcompression
1250 bversion = 'HG10' + bcompression
1251 bcompression = None
1251 bcompression = None
1252 elif cgversion in ('02', '03'):
1252 elif cgversion in ('02', '03'):
1253 bversion = 'HG20'
1253 bversion = 'HG20'
1254 else:
1254 else:
1255 raise error.ProgrammingError(
1255 raise error.ProgrammingError(
1256 'bundle: unexpected changegroup version %s' % cgversion)
1256 'bundle: unexpected changegroup version %s' % cgversion)
1257
1257
1258 # TODO compression options should be derived from bundlespec parsing.
1258 # TODO compression options should be derived from bundlespec parsing.
1259 # This is a temporary hack to allow adjusting bundle compression
1259 # This is a temporary hack to allow adjusting bundle compression
1260 # level without a) formalizing the bundlespec changes to declare it
1260 # level without a) formalizing the bundlespec changes to declare it
1261 # b) introducing a command flag.
1261 # b) introducing a command flag.
1262 compopts = {}
1262 compopts = {}
1263 complevel = ui.configint('experimental', 'bundlecomplevel')
1263 complevel = ui.configint('experimental', 'bundlecomplevel')
1264 if complevel is not None:
1264 if complevel is not None:
1265 compopts['level'] = complevel
1265 compopts['level'] = complevel
1266
1266
1267 # Allow overriding the bundling of obsmarker in phases through
1267 # Allow overriding the bundling of obsmarker in phases through
1268 # configuration while we don't have a bundle version that include them
1268 # configuration while we don't have a bundle version that include them
1269 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1269 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1270 bundlespec.contentopts['obsolescence'] = True
1270 bundlespec.contentopts['obsolescence'] = True
1271 if repo.ui.configbool('experimental', 'bundle-phases'):
1271 if repo.ui.configbool('experimental', 'bundle-phases'):
1272 bundlespec.contentopts['phases'] = True
1272 bundlespec.contentopts['phases'] = True
1273
1273
1274 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1274 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1275 bundlespec.contentopts, compression=bcompression,
1275 bundlespec.contentopts, compression=bcompression,
1276 compopts=compopts)
1276 compopts=compopts)
1277
1277
1278 @command('cat',
1278 @command('cat',
1279 [('o', 'output', '',
1279 [('o', 'output', '',
1280 _('print output to file with formatted name'), _('FORMAT')),
1280 _('print output to file with formatted name'), _('FORMAT')),
1281 ('r', 'rev', '', _('print the given revision'), _('REV')),
1281 ('r', 'rev', '', _('print the given revision'), _('REV')),
1282 ('', 'decode', None, _('apply any matching decode filter')),
1282 ('', 'decode', None, _('apply any matching decode filter')),
1283 ] + walkopts + formatteropts,
1283 ] + walkopts + formatteropts,
1284 _('[OPTION]... FILE...'),
1284 _('[OPTION]... FILE...'),
1285 inferrepo=True, cmdtype=readonly)
1285 inferrepo=True, cmdtype=readonly)
1286 def cat(ui, repo, file1, *pats, **opts):
1286 def cat(ui, repo, file1, *pats, **opts):
1287 """output the current or given revision of files
1287 """output the current or given revision of files
1288
1288
1289 Print the specified files as they were at the given revision. If
1289 Print the specified files as they were at the given revision. If
1290 no revision is given, the parent of the working directory is used.
1290 no revision is given, the parent of the working directory is used.
1291
1291
1292 Output may be to a file, in which case the name of the file is
1292 Output may be to a file, in which case the name of the file is
1293 given using a template string. See :hg:`help templates`. In addition
1293 given using a template string. See :hg:`help templates`. In addition
1294 to the common template keywords, the following formatting rules are
1294 to the common template keywords, the following formatting rules are
1295 supported:
1295 supported:
1296
1296
1297 :``%%``: literal "%" character
1297 :``%%``: literal "%" character
1298 :``%s``: basename of file being printed
1298 :``%s``: basename of file being printed
1299 :``%d``: dirname of file being printed, or '.' if in repository root
1299 :``%d``: dirname of file being printed, or '.' if in repository root
1300 :``%p``: root-relative path name of file being printed
1300 :``%p``: root-relative path name of file being printed
1301 :``%H``: changeset hash (40 hexadecimal digits)
1301 :``%H``: changeset hash (40 hexadecimal digits)
1302 :``%R``: changeset revision number
1302 :``%R``: changeset revision number
1303 :``%h``: short-form changeset hash (12 hexadecimal digits)
1303 :``%h``: short-form changeset hash (12 hexadecimal digits)
1304 :``%r``: zero-padded changeset revision number
1304 :``%r``: zero-padded changeset revision number
1305 :``%b``: basename of the exporting repository
1305 :``%b``: basename of the exporting repository
1306 :``\\``: literal "\\" character
1306 :``\\``: literal "\\" character
1307
1307
1308 Returns 0 on success.
1308 Returns 0 on success.
1309 """
1309 """
1310 opts = pycompat.byteskwargs(opts)
1310 opts = pycompat.byteskwargs(opts)
1311 rev = opts.get('rev')
1311 rev = opts.get('rev')
1312 if rev:
1312 if rev:
1313 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1313 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1314 ctx = scmutil.revsingle(repo, rev)
1314 ctx = scmutil.revsingle(repo, rev)
1315 m = scmutil.match(ctx, (file1,) + pats, opts)
1315 m = scmutil.match(ctx, (file1,) + pats, opts)
1316 fntemplate = opts.pop('output', '')
1316 fntemplate = opts.pop('output', '')
1317 if cmdutil.isstdiofilename(fntemplate):
1317 if cmdutil.isstdiofilename(fntemplate):
1318 fntemplate = ''
1318 fntemplate = ''
1319
1319
1320 if fntemplate:
1320 if fntemplate:
1321 fm = formatter.nullformatter(ui, 'cat', opts)
1321 fm = formatter.nullformatter(ui, 'cat', opts)
1322 else:
1322 else:
1323 ui.pager('cat')
1323 ui.pager('cat')
1324 fm = ui.formatter('cat', opts)
1324 fm = ui.formatter('cat', opts)
1325 with fm:
1325 with fm:
1326 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1326 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1327 **pycompat.strkwargs(opts))
1327 **pycompat.strkwargs(opts))
1328
1328
1329 @command('^clone',
1329 @command('^clone',
1330 [('U', 'noupdate', None, _('the clone will include an empty working '
1330 [('U', 'noupdate', None, _('the clone will include an empty working '
1331 'directory (only a repository)')),
1331 'directory (only a repository)')),
1332 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1332 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1333 _('REV')),
1333 _('REV')),
1334 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1334 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1335 ' and its ancestors'), _('REV')),
1335 ' and its ancestors'), _('REV')),
1336 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1336 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1337 ' changesets and their ancestors'), _('BRANCH')),
1337 ' changesets and their ancestors'), _('BRANCH')),
1338 ('', 'pull', None, _('use pull protocol to copy metadata')),
1338 ('', 'pull', None, _('use pull protocol to copy metadata')),
1339 ('', 'uncompressed', None,
1339 ('', 'uncompressed', None,
1340 _('an alias to --stream (DEPRECATED)')),
1340 _('an alias to --stream (DEPRECATED)')),
1341 ('', 'stream', None,
1341 ('', 'stream', None,
1342 _('clone with minimal data processing')),
1342 _('clone with minimal data processing')),
1343 ] + remoteopts,
1343 ] + remoteopts,
1344 _('[OPTION]... SOURCE [DEST]'),
1344 _('[OPTION]... SOURCE [DEST]'),
1345 norepo=True)
1345 norepo=True)
1346 def clone(ui, source, dest=None, **opts):
1346 def clone(ui, source, dest=None, **opts):
1347 """make a copy of an existing repository
1347 """make a copy of an existing repository
1348
1348
1349 Create a copy of an existing repository in a new directory.
1349 Create a copy of an existing repository in a new directory.
1350
1350
1351 If no destination directory name is specified, it defaults to the
1351 If no destination directory name is specified, it defaults to the
1352 basename of the source.
1352 basename of the source.
1353
1353
1354 The location of the source is added to the new repository's
1354 The location of the source is added to the new repository's
1355 ``.hg/hgrc`` file, as the default to be used for future pulls.
1355 ``.hg/hgrc`` file, as the default to be used for future pulls.
1356
1356
1357 Only local paths and ``ssh://`` URLs are supported as
1357 Only local paths and ``ssh://`` URLs are supported as
1358 destinations. For ``ssh://`` destinations, no working directory or
1358 destinations. For ``ssh://`` destinations, no working directory or
1359 ``.hg/hgrc`` will be created on the remote side.
1359 ``.hg/hgrc`` will be created on the remote side.
1360
1360
1361 If the source repository has a bookmark called '@' set, that
1361 If the source repository has a bookmark called '@' set, that
1362 revision will be checked out in the new repository by default.
1362 revision will be checked out in the new repository by default.
1363
1363
1364 To check out a particular version, use -u/--update, or
1364 To check out a particular version, use -u/--update, or
1365 -U/--noupdate to create a clone with no working directory.
1365 -U/--noupdate to create a clone with no working directory.
1366
1366
1367 To pull only a subset of changesets, specify one or more revisions
1367 To pull only a subset of changesets, specify one or more revisions
1368 identifiers with -r/--rev or branches with -b/--branch. The
1368 identifiers with -r/--rev or branches with -b/--branch. The
1369 resulting clone will contain only the specified changesets and
1369 resulting clone will contain only the specified changesets and
1370 their ancestors. These options (or 'clone src#rev dest') imply
1370 their ancestors. These options (or 'clone src#rev dest') imply
1371 --pull, even for local source repositories.
1371 --pull, even for local source repositories.
1372
1372
1373 In normal clone mode, the remote normalizes repository data into a common
1373 In normal clone mode, the remote normalizes repository data into a common
1374 exchange format and the receiving end translates this data into its local
1374 exchange format and the receiving end translates this data into its local
1375 storage format. --stream activates a different clone mode that essentially
1375 storage format. --stream activates a different clone mode that essentially
1376 copies repository files from the remote with minimal data processing. This
1376 copies repository files from the remote with minimal data processing. This
1377 significantly reduces the CPU cost of a clone both remotely and locally.
1377 significantly reduces the CPU cost of a clone both remotely and locally.
1378 However, it often increases the transferred data size by 30-40%. This can
1378 However, it often increases the transferred data size by 30-40%. This can
1379 result in substantially faster clones where I/O throughput is plentiful,
1379 result in substantially faster clones where I/O throughput is plentiful,
1380 especially for larger repositories. A side-effect of --stream clones is
1380 especially for larger repositories. A side-effect of --stream clones is
1381 that storage settings and requirements on the remote are applied locally:
1381 that storage settings and requirements on the remote are applied locally:
1382 a modern client may inherit legacy or inefficient storage used by the
1382 a modern client may inherit legacy or inefficient storage used by the
1383 remote or a legacy Mercurial client may not be able to clone from a
1383 remote or a legacy Mercurial client may not be able to clone from a
1384 modern Mercurial remote.
1384 modern Mercurial remote.
1385
1385
1386 .. note::
1386 .. note::
1387
1387
1388 Specifying a tag will include the tagged changeset but not the
1388 Specifying a tag will include the tagged changeset but not the
1389 changeset containing the tag.
1389 changeset containing the tag.
1390
1390
1391 .. container:: verbose
1391 .. container:: verbose
1392
1392
1393 For efficiency, hardlinks are used for cloning whenever the
1393 For efficiency, hardlinks are used for cloning whenever the
1394 source and destination are on the same filesystem (note this
1394 source and destination are on the same filesystem (note this
1395 applies only to the repository data, not to the working
1395 applies only to the repository data, not to the working
1396 directory). Some filesystems, such as AFS, implement hardlinking
1396 directory). Some filesystems, such as AFS, implement hardlinking
1397 incorrectly, but do not report errors. In these cases, use the
1397 incorrectly, but do not report errors. In these cases, use the
1398 --pull option to avoid hardlinking.
1398 --pull option to avoid hardlinking.
1399
1399
1400 Mercurial will update the working directory to the first applicable
1400 Mercurial will update the working directory to the first applicable
1401 revision from this list:
1401 revision from this list:
1402
1402
1403 a) null if -U or the source repository has no changesets
1403 a) null if -U or the source repository has no changesets
1404 b) if -u . and the source repository is local, the first parent of
1404 b) if -u . and the source repository is local, the first parent of
1405 the source repository's working directory
1405 the source repository's working directory
1406 c) the changeset specified with -u (if a branch name, this means the
1406 c) the changeset specified with -u (if a branch name, this means the
1407 latest head of that branch)
1407 latest head of that branch)
1408 d) the changeset specified with -r
1408 d) the changeset specified with -r
1409 e) the tipmost head specified with -b
1409 e) the tipmost head specified with -b
1410 f) the tipmost head specified with the url#branch source syntax
1410 f) the tipmost head specified with the url#branch source syntax
1411 g) the revision marked with the '@' bookmark, if present
1411 g) the revision marked with the '@' bookmark, if present
1412 h) the tipmost head of the default branch
1412 h) the tipmost head of the default branch
1413 i) tip
1413 i) tip
1414
1414
1415 When cloning from servers that support it, Mercurial may fetch
1415 When cloning from servers that support it, Mercurial may fetch
1416 pre-generated data from a server-advertised URL or inline from the
1416 pre-generated data from a server-advertised URL or inline from the
1417 same stream. When this is done, hooks operating on incoming changesets
1417 same stream. When this is done, hooks operating on incoming changesets
1418 and changegroups may fire more than once, once for each pre-generated
1418 and changegroups may fire more than once, once for each pre-generated
1419 bundle and as well as for any additional remaining data. In addition,
1419 bundle and as well as for any additional remaining data. In addition,
1420 if an error occurs, the repository may be rolled back to a partial
1420 if an error occurs, the repository may be rolled back to a partial
1421 clone. This behavior may change in future releases.
1421 clone. This behavior may change in future releases.
1422 See :hg:`help -e clonebundles` for more.
1422 See :hg:`help -e clonebundles` for more.
1423
1423
1424 Examples:
1424 Examples:
1425
1425
1426 - clone a remote repository to a new directory named hg/::
1426 - clone a remote repository to a new directory named hg/::
1427
1427
1428 hg clone https://www.mercurial-scm.org/repo/hg/
1428 hg clone https://www.mercurial-scm.org/repo/hg/
1429
1429
1430 - create a lightweight local clone::
1430 - create a lightweight local clone::
1431
1431
1432 hg clone project/ project-feature/
1432 hg clone project/ project-feature/
1433
1433
1434 - clone from an absolute path on an ssh server (note double-slash)::
1434 - clone from an absolute path on an ssh server (note double-slash)::
1435
1435
1436 hg clone ssh://user@server//home/projects/alpha/
1436 hg clone ssh://user@server//home/projects/alpha/
1437
1437
1438 - do a streaming clone while checking out a specified version::
1438 - do a streaming clone while checking out a specified version::
1439
1439
1440 hg clone --stream http://server/repo -u 1.5
1440 hg clone --stream http://server/repo -u 1.5
1441
1441
1442 - create a repository without changesets after a particular revision::
1442 - create a repository without changesets after a particular revision::
1443
1443
1444 hg clone -r 04e544 experimental/ good/
1444 hg clone -r 04e544 experimental/ good/
1445
1445
1446 - clone (and track) a particular named branch::
1446 - clone (and track) a particular named branch::
1447
1447
1448 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1448 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1449
1449
1450 See :hg:`help urls` for details on specifying URLs.
1450 See :hg:`help urls` for details on specifying URLs.
1451
1451
1452 Returns 0 on success.
1452 Returns 0 on success.
1453 """
1453 """
1454 opts = pycompat.byteskwargs(opts)
1454 opts = pycompat.byteskwargs(opts)
1455 if opts.get('noupdate') and opts.get('updaterev'):
1455 if opts.get('noupdate') and opts.get('updaterev'):
1456 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1456 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1457
1457
1458 r = hg.clone(ui, opts, source, dest,
1458 r = hg.clone(ui, opts, source, dest,
1459 pull=opts.get('pull'),
1459 pull=opts.get('pull'),
1460 stream=opts.get('stream') or opts.get('uncompressed'),
1460 stream=opts.get('stream') or opts.get('uncompressed'),
1461 revs=opts.get('rev'),
1461 revs=opts.get('rev'),
1462 update=opts.get('updaterev') or not opts.get('noupdate'),
1462 update=opts.get('updaterev') or not opts.get('noupdate'),
1463 branch=opts.get('branch'),
1463 branch=opts.get('branch'),
1464 shareopts=opts.get('shareopts'))
1464 shareopts=opts.get('shareopts'))
1465
1465
1466 return r is None
1466 return r is None
1467
1467
1468 @command('^commit|ci',
1468 @command('^commit|ci',
1469 [('A', 'addremove', None,
1469 [('A', 'addremove', None,
1470 _('mark new/missing files as added/removed before committing')),
1470 _('mark new/missing files as added/removed before committing')),
1471 ('', 'close-branch', None,
1471 ('', 'close-branch', None,
1472 _('mark a branch head as closed')),
1472 _('mark a branch head as closed')),
1473 ('', 'amend', None, _('amend the parent of the working directory')),
1473 ('', 'amend', None, _('amend the parent of the working directory')),
1474 ('s', 'secret', None, _('use the secret phase for committing')),
1474 ('s', 'secret', None, _('use the secret phase for committing')),
1475 ('e', 'edit', None, _('invoke editor on commit messages')),
1475 ('e', 'edit', None, _('invoke editor on commit messages')),
1476 ('i', 'interactive', None, _('use interactive mode')),
1476 ('i', 'interactive', None, _('use interactive mode')),
1477 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1477 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1478 _('[OPTION]... [FILE]...'),
1478 _('[OPTION]... [FILE]...'),
1479 inferrepo=True)
1479 inferrepo=True)
1480 def commit(ui, repo, *pats, **opts):
1480 def commit(ui, repo, *pats, **opts):
1481 """commit the specified files or all outstanding changes
1481 """commit the specified files or all outstanding changes
1482
1482
1483 Commit changes to the given files into the repository. Unlike a
1483 Commit changes to the given files into the repository. Unlike a
1484 centralized SCM, this operation is a local operation. See
1484 centralized SCM, this operation is a local operation. See
1485 :hg:`push` for a way to actively distribute your changes.
1485 :hg:`push` for a way to actively distribute your changes.
1486
1486
1487 If a list of files is omitted, all changes reported by :hg:`status`
1487 If a list of files is omitted, all changes reported by :hg:`status`
1488 will be committed.
1488 will be committed.
1489
1489
1490 If you are committing the result of a merge, do not provide any
1490 If you are committing the result of a merge, do not provide any
1491 filenames or -I/-X filters.
1491 filenames or -I/-X filters.
1492
1492
1493 If no commit message is specified, Mercurial starts your
1493 If no commit message is specified, Mercurial starts your
1494 configured editor where you can enter a message. In case your
1494 configured editor where you can enter a message. In case your
1495 commit fails, you will find a backup of your message in
1495 commit fails, you will find a backup of your message in
1496 ``.hg/last-message.txt``.
1496 ``.hg/last-message.txt``.
1497
1497
1498 The --close-branch flag can be used to mark the current branch
1498 The --close-branch flag can be used to mark the current branch
1499 head closed. When all heads of a branch are closed, the branch
1499 head closed. When all heads of a branch are closed, the branch
1500 will be considered closed and no longer listed.
1500 will be considered closed and no longer listed.
1501
1501
1502 The --amend flag can be used to amend the parent of the
1502 The --amend flag can be used to amend the parent of the
1503 working directory with a new commit that contains the changes
1503 working directory with a new commit that contains the changes
1504 in the parent in addition to those currently reported by :hg:`status`,
1504 in the parent in addition to those currently reported by :hg:`status`,
1505 if there are any. The old commit is stored in a backup bundle in
1505 if there are any. The old commit is stored in a backup bundle in
1506 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1506 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1507 on how to restore it).
1507 on how to restore it).
1508
1508
1509 Message, user and date are taken from the amended commit unless
1509 Message, user and date are taken from the amended commit unless
1510 specified. When a message isn't specified on the command line,
1510 specified. When a message isn't specified on the command line,
1511 the editor will open with the message of the amended commit.
1511 the editor will open with the message of the amended commit.
1512
1512
1513 It is not possible to amend public changesets (see :hg:`help phases`)
1513 It is not possible to amend public changesets (see :hg:`help phases`)
1514 or changesets that have children.
1514 or changesets that have children.
1515
1515
1516 See :hg:`help dates` for a list of formats valid for -d/--date.
1516 See :hg:`help dates` for a list of formats valid for -d/--date.
1517
1517
1518 Returns 0 on success, 1 if nothing changed.
1518 Returns 0 on success, 1 if nothing changed.
1519
1519
1520 .. container:: verbose
1520 .. container:: verbose
1521
1521
1522 Examples:
1522 Examples:
1523
1523
1524 - commit all files ending in .py::
1524 - commit all files ending in .py::
1525
1525
1526 hg commit --include "set:**.py"
1526 hg commit --include "set:**.py"
1527
1527
1528 - commit all non-binary files::
1528 - commit all non-binary files::
1529
1529
1530 hg commit --exclude "set:binary()"
1530 hg commit --exclude "set:binary()"
1531
1531
1532 - amend the current commit and set the date to now::
1532 - amend the current commit and set the date to now::
1533
1533
1534 hg commit --amend --date now
1534 hg commit --amend --date now
1535 """
1535 """
1536 wlock = lock = None
1536 wlock = lock = None
1537 try:
1537 try:
1538 wlock = repo.wlock()
1538 wlock = repo.wlock()
1539 lock = repo.lock()
1539 lock = repo.lock()
1540 return _docommit(ui, repo, *pats, **opts)
1540 return _docommit(ui, repo, *pats, **opts)
1541 finally:
1541 finally:
1542 release(lock, wlock)
1542 release(lock, wlock)
1543
1543
1544 def _docommit(ui, repo, *pats, **opts):
1544 def _docommit(ui, repo, *pats, **opts):
1545 if opts.get(r'interactive'):
1545 if opts.get(r'interactive'):
1546 opts.pop(r'interactive')
1546 opts.pop(r'interactive')
1547 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1547 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1548 cmdutil.recordfilter, *pats,
1548 cmdutil.recordfilter, *pats,
1549 **opts)
1549 **opts)
1550 # ret can be 0 (no changes to record) or the value returned by
1550 # ret can be 0 (no changes to record) or the value returned by
1551 # commit(), 1 if nothing changed or None on success.
1551 # commit(), 1 if nothing changed or None on success.
1552 return 1 if ret == 0 else ret
1552 return 1 if ret == 0 else ret
1553
1553
1554 opts = pycompat.byteskwargs(opts)
1554 opts = pycompat.byteskwargs(opts)
1555 if opts.get('subrepos'):
1555 if opts.get('subrepos'):
1556 if opts.get('amend'):
1556 if opts.get('amend'):
1557 raise error.Abort(_('cannot amend with --subrepos'))
1557 raise error.Abort(_('cannot amend with --subrepos'))
1558 # Let --subrepos on the command line override config setting.
1558 # Let --subrepos on the command line override config setting.
1559 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1559 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1560
1560
1561 cmdutil.checkunfinished(repo, commit=True)
1561 cmdutil.checkunfinished(repo, commit=True)
1562
1562
1563 branch = repo[None].branch()
1563 branch = repo[None].branch()
1564 bheads = repo.branchheads(branch)
1564 bheads = repo.branchheads(branch)
1565
1565
1566 extra = {}
1566 extra = {}
1567 if opts.get('close_branch'):
1567 if opts.get('close_branch'):
1568 extra['close'] = '1'
1568 extra['close'] = '1'
1569
1569
1570 if not bheads:
1570 if not bheads:
1571 raise error.Abort(_('can only close branch heads'))
1571 raise error.Abort(_('can only close branch heads'))
1572 elif opts.get('amend'):
1572 elif opts.get('amend'):
1573 if repo[None].parents()[0].p1().branch() != branch and \
1573 if repo[None].parents()[0].p1().branch() != branch and \
1574 repo[None].parents()[0].p2().branch() != branch:
1574 repo[None].parents()[0].p2().branch() != branch:
1575 raise error.Abort(_('can only close branch heads'))
1575 raise error.Abort(_('can only close branch heads'))
1576
1576
1577 if opts.get('amend'):
1577 if opts.get('amend'):
1578 if ui.configbool('ui', 'commitsubrepos'):
1578 if ui.configbool('ui', 'commitsubrepos'):
1579 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1579 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1580
1580
1581 old = repo['.']
1581 old = repo['.']
1582 rewriteutil.precheck(repo, [old.rev()], 'amend')
1582 rewriteutil.precheck(repo, [old.rev()], 'amend')
1583
1583
1584 # Currently histedit gets confused if an amend happens while histedit
1584 # Currently histedit gets confused if an amend happens while histedit
1585 # is in progress. Since we have a checkunfinished command, we are
1585 # is in progress. Since we have a checkunfinished command, we are
1586 # temporarily honoring it.
1586 # temporarily honoring it.
1587 #
1587 #
1588 # Note: eventually this guard will be removed. Please do not expect
1588 # Note: eventually this guard will be removed. Please do not expect
1589 # this behavior to remain.
1589 # this behavior to remain.
1590 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1590 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1591 cmdutil.checkunfinished(repo)
1591 cmdutil.checkunfinished(repo)
1592
1592
1593 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1593 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1594 if node == old.node():
1594 if node == old.node():
1595 ui.status(_("nothing changed\n"))
1595 ui.status(_("nothing changed\n"))
1596 return 1
1596 return 1
1597 else:
1597 else:
1598 def commitfunc(ui, repo, message, match, opts):
1598 def commitfunc(ui, repo, message, match, opts):
1599 overrides = {}
1599 overrides = {}
1600 if opts.get('secret'):
1600 if opts.get('secret'):
1601 overrides[('phases', 'new-commit')] = 'secret'
1601 overrides[('phases', 'new-commit')] = 'secret'
1602
1602
1603 baseui = repo.baseui
1603 baseui = repo.baseui
1604 with baseui.configoverride(overrides, 'commit'):
1604 with baseui.configoverride(overrides, 'commit'):
1605 with ui.configoverride(overrides, 'commit'):
1605 with ui.configoverride(overrides, 'commit'):
1606 editform = cmdutil.mergeeditform(repo[None],
1606 editform = cmdutil.mergeeditform(repo[None],
1607 'commit.normal')
1607 'commit.normal')
1608 editor = cmdutil.getcommiteditor(
1608 editor = cmdutil.getcommiteditor(
1609 editform=editform, **pycompat.strkwargs(opts))
1609 editform=editform, **pycompat.strkwargs(opts))
1610 return repo.commit(message,
1610 return repo.commit(message,
1611 opts.get('user'),
1611 opts.get('user'),
1612 opts.get('date'),
1612 opts.get('date'),
1613 match,
1613 match,
1614 editor=editor,
1614 editor=editor,
1615 extra=extra)
1615 extra=extra)
1616
1616
1617 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1617 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1618
1618
1619 if not node:
1619 if not node:
1620 stat = cmdutil.postcommitstatus(repo, pats, opts)
1620 stat = cmdutil.postcommitstatus(repo, pats, opts)
1621 if stat[3]:
1621 if stat[3]:
1622 ui.status(_("nothing changed (%d missing files, see "
1622 ui.status(_("nothing changed (%d missing files, see "
1623 "'hg status')\n") % len(stat[3]))
1623 "'hg status')\n") % len(stat[3]))
1624 else:
1624 else:
1625 ui.status(_("nothing changed\n"))
1625 ui.status(_("nothing changed\n"))
1626 return 1
1626 return 1
1627
1627
1628 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1628 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1629
1629
1630 @command('config|showconfig|debugconfig',
1630 @command('config|showconfig|debugconfig',
1631 [('u', 'untrusted', None, _('show untrusted configuration options')),
1631 [('u', 'untrusted', None, _('show untrusted configuration options')),
1632 ('e', 'edit', None, _('edit user config')),
1632 ('e', 'edit', None, _('edit user config')),
1633 ('l', 'local', None, _('edit repository config')),
1633 ('l', 'local', None, _('edit repository config')),
1634 ('g', 'global', None, _('edit global config'))] + formatteropts,
1634 ('g', 'global', None, _('edit global config'))] + formatteropts,
1635 _('[-u] [NAME]...'),
1635 _('[-u] [NAME]...'),
1636 optionalrepo=True, cmdtype=readonly)
1636 optionalrepo=True, cmdtype=readonly)
1637 def config(ui, repo, *values, **opts):
1637 def config(ui, repo, *values, **opts):
1638 """show combined config settings from all hgrc files
1638 """show combined config settings from all hgrc files
1639
1639
1640 With no arguments, print names and values of all config items.
1640 With no arguments, print names and values of all config items.
1641
1641
1642 With one argument of the form section.name, print just the value
1642 With one argument of the form section.name, print just the value
1643 of that config item.
1643 of that config item.
1644
1644
1645 With multiple arguments, print names and values of all config
1645 With multiple arguments, print names and values of all config
1646 items with matching section names or section.names.
1646 items with matching section names or section.names.
1647
1647
1648 With --edit, start an editor on the user-level config file. With
1648 With --edit, start an editor on the user-level config file. With
1649 --global, edit the system-wide config file. With --local, edit the
1649 --global, edit the system-wide config file. With --local, edit the
1650 repository-level config file.
1650 repository-level config file.
1651
1651
1652 With --debug, the source (filename and line number) is printed
1652 With --debug, the source (filename and line number) is printed
1653 for each config item.
1653 for each config item.
1654
1654
1655 See :hg:`help config` for more information about config files.
1655 See :hg:`help config` for more information about config files.
1656
1656
1657 Returns 0 on success, 1 if NAME does not exist.
1657 Returns 0 on success, 1 if NAME does not exist.
1658
1658
1659 """
1659 """
1660
1660
1661 opts = pycompat.byteskwargs(opts)
1661 opts = pycompat.byteskwargs(opts)
1662 if opts.get('edit') or opts.get('local') or opts.get('global'):
1662 if opts.get('edit') or opts.get('local') or opts.get('global'):
1663 if opts.get('local') and opts.get('global'):
1663 if opts.get('local') and opts.get('global'):
1664 raise error.Abort(_("can't use --local and --global together"))
1664 raise error.Abort(_("can't use --local and --global together"))
1665
1665
1666 if opts.get('local'):
1666 if opts.get('local'):
1667 if not repo:
1667 if not repo:
1668 raise error.Abort(_("can't use --local outside a repository"))
1668 raise error.Abort(_("can't use --local outside a repository"))
1669 paths = [repo.vfs.join('hgrc')]
1669 paths = [repo.vfs.join('hgrc')]
1670 elif opts.get('global'):
1670 elif opts.get('global'):
1671 paths = rcutil.systemrcpath()
1671 paths = rcutil.systemrcpath()
1672 else:
1672 else:
1673 paths = rcutil.userrcpath()
1673 paths = rcutil.userrcpath()
1674
1674
1675 for f in paths:
1675 for f in paths:
1676 if os.path.exists(f):
1676 if os.path.exists(f):
1677 break
1677 break
1678 else:
1678 else:
1679 if opts.get('global'):
1679 if opts.get('global'):
1680 samplehgrc = uimod.samplehgrcs['global']
1680 samplehgrc = uimod.samplehgrcs['global']
1681 elif opts.get('local'):
1681 elif opts.get('local'):
1682 samplehgrc = uimod.samplehgrcs['local']
1682 samplehgrc = uimod.samplehgrcs['local']
1683 else:
1683 else:
1684 samplehgrc = uimod.samplehgrcs['user']
1684 samplehgrc = uimod.samplehgrcs['user']
1685
1685
1686 f = paths[0]
1686 f = paths[0]
1687 fp = open(f, "wb")
1687 fp = open(f, "wb")
1688 fp.write(util.tonativeeol(samplehgrc))
1688 fp.write(util.tonativeeol(samplehgrc))
1689 fp.close()
1689 fp.close()
1690
1690
1691 editor = ui.geteditor()
1691 editor = ui.geteditor()
1692 ui.system("%s \"%s\"" % (editor, f),
1692 ui.system("%s \"%s\"" % (editor, f),
1693 onerr=error.Abort, errprefix=_("edit failed"),
1693 onerr=error.Abort, errprefix=_("edit failed"),
1694 blockedtag='config_edit')
1694 blockedtag='config_edit')
1695 return
1695 return
1696 ui.pager('config')
1696 ui.pager('config')
1697 fm = ui.formatter('config', opts)
1697 fm = ui.formatter('config', opts)
1698 for t, f in rcutil.rccomponents():
1698 for t, f in rcutil.rccomponents():
1699 if t == 'path':
1699 if t == 'path':
1700 ui.debug('read config from: %s\n' % f)
1700 ui.debug('read config from: %s\n' % f)
1701 elif t == 'items':
1701 elif t == 'items':
1702 for section, name, value, source in f:
1702 for section, name, value, source in f:
1703 ui.debug('set config by: %s\n' % source)
1703 ui.debug('set config by: %s\n' % source)
1704 else:
1704 else:
1705 raise error.ProgrammingError('unknown rctype: %s' % t)
1705 raise error.ProgrammingError('unknown rctype: %s' % t)
1706 untrusted = bool(opts.get('untrusted'))
1706 untrusted = bool(opts.get('untrusted'))
1707
1707
1708 selsections = selentries = []
1708 selsections = selentries = []
1709 if values:
1709 if values:
1710 selsections = [v for v in values if '.' not in v]
1710 selsections = [v for v in values if '.' not in v]
1711 selentries = [v for v in values if '.' in v]
1711 selentries = [v for v in values if '.' in v]
1712 uniquesel = (len(selentries) == 1 and not selsections)
1712 uniquesel = (len(selentries) == 1 and not selsections)
1713 selsections = set(selsections)
1713 selsections = set(selsections)
1714 selentries = set(selentries)
1714 selentries = set(selentries)
1715
1715
1716 matched = False
1716 matched = False
1717 for section, name, value in ui.walkconfig(untrusted=untrusted):
1717 for section, name, value in ui.walkconfig(untrusted=untrusted):
1718 source = ui.configsource(section, name, untrusted)
1718 source = ui.configsource(section, name, untrusted)
1719 value = pycompat.bytestr(value)
1719 value = pycompat.bytestr(value)
1720 if fm.isplain():
1720 if fm.isplain():
1721 source = source or 'none'
1721 source = source or 'none'
1722 value = value.replace('\n', '\\n')
1722 value = value.replace('\n', '\\n')
1723 entryname = section + '.' + name
1723 entryname = section + '.' + name
1724 if values and not (section in selsections or entryname in selentries):
1724 if values and not (section in selsections or entryname in selentries):
1725 continue
1725 continue
1726 fm.startitem()
1726 fm.startitem()
1727 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1727 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1728 if uniquesel:
1728 if uniquesel:
1729 fm.data(name=entryname)
1729 fm.data(name=entryname)
1730 fm.write('value', '%s\n', value)
1730 fm.write('value', '%s\n', value)
1731 else:
1731 else:
1732 fm.write('name value', '%s=%s\n', entryname, value)
1732 fm.write('name value', '%s=%s\n', entryname, value)
1733 matched = True
1733 matched = True
1734 fm.end()
1734 fm.end()
1735 if matched:
1735 if matched:
1736 return 0
1736 return 0
1737 return 1
1737 return 1
1738
1738
1739 @command('copy|cp',
1739 @command('copy|cp',
1740 [('A', 'after', None, _('record a copy that has already occurred')),
1740 [('A', 'after', None, _('record a copy that has already occurred')),
1741 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1741 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1742 ] + walkopts + dryrunopts,
1742 ] + walkopts + dryrunopts,
1743 _('[OPTION]... [SOURCE]... DEST'))
1743 _('[OPTION]... [SOURCE]... DEST'))
1744 def copy(ui, repo, *pats, **opts):
1744 def copy(ui, repo, *pats, **opts):
1745 """mark files as copied for the next commit
1745 """mark files as copied for the next commit
1746
1746
1747 Mark dest as having copies of source files. If dest is a
1747 Mark dest as having copies of source files. If dest is a
1748 directory, copies are put in that directory. If dest is a file,
1748 directory, copies are put in that directory. If dest is a file,
1749 the source must be a single file.
1749 the source must be a single file.
1750
1750
1751 By default, this command copies the contents of files as they
1751 By default, this command copies the contents of files as they
1752 exist in the working directory. If invoked with -A/--after, the
1752 exist in the working directory. If invoked with -A/--after, the
1753 operation is recorded, but no copying is performed.
1753 operation is recorded, but no copying is performed.
1754
1754
1755 This command takes effect with the next commit. To undo a copy
1755 This command takes effect with the next commit. To undo a copy
1756 before that, see :hg:`revert`.
1756 before that, see :hg:`revert`.
1757
1757
1758 Returns 0 on success, 1 if errors are encountered.
1758 Returns 0 on success, 1 if errors are encountered.
1759 """
1759 """
1760 opts = pycompat.byteskwargs(opts)
1760 opts = pycompat.byteskwargs(opts)
1761 with repo.wlock(False):
1761 with repo.wlock(False):
1762 return cmdutil.copy(ui, repo, pats, opts)
1762 return cmdutil.copy(ui, repo, pats, opts)
1763
1763
1764 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1764 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1765 def debugcommands(ui, cmd='', *args):
1765 def debugcommands(ui, cmd='', *args):
1766 """list all available commands and options"""
1766 """list all available commands and options"""
1767 for cmd, vals in sorted(table.iteritems()):
1767 for cmd, vals in sorted(table.iteritems()):
1768 cmd = cmd.split('|')[0].strip('^')
1768 cmd = cmd.split('|')[0].strip('^')
1769 opts = ', '.join([i[1] for i in vals[1]])
1769 opts = ', '.join([i[1] for i in vals[1]])
1770 ui.write('%s: %s\n' % (cmd, opts))
1770 ui.write('%s: %s\n' % (cmd, opts))
1771
1771
1772 @command('debugcomplete',
1772 @command('debugcomplete',
1773 [('o', 'options', None, _('show the command options'))],
1773 [('o', 'options', None, _('show the command options'))],
1774 _('[-o] CMD'),
1774 _('[-o] CMD'),
1775 norepo=True)
1775 norepo=True)
1776 def debugcomplete(ui, cmd='', **opts):
1776 def debugcomplete(ui, cmd='', **opts):
1777 """returns the completion list associated with the given command"""
1777 """returns the completion list associated with the given command"""
1778
1778
1779 if opts.get(r'options'):
1779 if opts.get(r'options'):
1780 options = []
1780 options = []
1781 otables = [globalopts]
1781 otables = [globalopts]
1782 if cmd:
1782 if cmd:
1783 aliases, entry = cmdutil.findcmd(cmd, table, False)
1783 aliases, entry = cmdutil.findcmd(cmd, table, False)
1784 otables.append(entry[1])
1784 otables.append(entry[1])
1785 for t in otables:
1785 for t in otables:
1786 for o in t:
1786 for o in t:
1787 if "(DEPRECATED)" in o[3]:
1787 if "(DEPRECATED)" in o[3]:
1788 continue
1788 continue
1789 if o[0]:
1789 if o[0]:
1790 options.append('-%s' % o[0])
1790 options.append('-%s' % o[0])
1791 options.append('--%s' % o[1])
1791 options.append('--%s' % o[1])
1792 ui.write("%s\n" % "\n".join(options))
1792 ui.write("%s\n" % "\n".join(options))
1793 return
1793 return
1794
1794
1795 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1795 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1796 if ui.verbose:
1796 if ui.verbose:
1797 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1797 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1798 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1798 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1799
1799
1800 @command('^diff',
1800 @command('^diff',
1801 [('r', 'rev', [], _('revision'), _('REV')),
1801 [('r', 'rev', [], _('revision'), _('REV')),
1802 ('c', 'change', '', _('change made by revision'), _('REV'))
1802 ('c', 'change', '', _('change made by revision'), _('REV'))
1803 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1803 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1804 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1804 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1805 inferrepo=True, cmdtype=readonly)
1805 inferrepo=True, cmdtype=readonly)
1806 def diff(ui, repo, *pats, **opts):
1806 def diff(ui, repo, *pats, **opts):
1807 """diff repository (or selected files)
1807 """diff repository (or selected files)
1808
1808
1809 Show differences between revisions for the specified files.
1809 Show differences between revisions for the specified files.
1810
1810
1811 Differences between files are shown using the unified diff format.
1811 Differences between files are shown using the unified diff format.
1812
1812
1813 .. note::
1813 .. note::
1814
1814
1815 :hg:`diff` may generate unexpected results for merges, as it will
1815 :hg:`diff` may generate unexpected results for merges, as it will
1816 default to comparing against the working directory's first
1816 default to comparing against the working directory's first
1817 parent changeset if no revisions are specified.
1817 parent changeset if no revisions are specified.
1818
1818
1819 When two revision arguments are given, then changes are shown
1819 When two revision arguments are given, then changes are shown
1820 between those revisions. If only one revision is specified then
1820 between those revisions. If only one revision is specified then
1821 that revision is compared to the working directory, and, when no
1821 that revision is compared to the working directory, and, when no
1822 revisions are specified, the working directory files are compared
1822 revisions are specified, the working directory files are compared
1823 to its first parent.
1823 to its first parent.
1824
1824
1825 Alternatively you can specify -c/--change with a revision to see
1825 Alternatively you can specify -c/--change with a revision to see
1826 the changes in that changeset relative to its first parent.
1826 the changes in that changeset relative to its first parent.
1827
1827
1828 Without the -a/--text option, diff will avoid generating diffs of
1828 Without the -a/--text option, diff will avoid generating diffs of
1829 files it detects as binary. With -a, diff will generate a diff
1829 files it detects as binary. With -a, diff will generate a diff
1830 anyway, probably with undesirable results.
1830 anyway, probably with undesirable results.
1831
1831
1832 Use the -g/--git option to generate diffs in the git extended diff
1832 Use the -g/--git option to generate diffs in the git extended diff
1833 format. For more information, read :hg:`help diffs`.
1833 format. For more information, read :hg:`help diffs`.
1834
1834
1835 .. container:: verbose
1835 .. container:: verbose
1836
1836
1837 Examples:
1837 Examples:
1838
1838
1839 - compare a file in the current working directory to its parent::
1839 - compare a file in the current working directory to its parent::
1840
1840
1841 hg diff foo.c
1841 hg diff foo.c
1842
1842
1843 - compare two historical versions of a directory, with rename info::
1843 - compare two historical versions of a directory, with rename info::
1844
1844
1845 hg diff --git -r 1.0:1.2 lib/
1845 hg diff --git -r 1.0:1.2 lib/
1846
1846
1847 - get change stats relative to the last change on some date::
1847 - get change stats relative to the last change on some date::
1848
1848
1849 hg diff --stat -r "date('may 2')"
1849 hg diff --stat -r "date('may 2')"
1850
1850
1851 - diff all newly-added files that contain a keyword::
1851 - diff all newly-added files that contain a keyword::
1852
1852
1853 hg diff "set:added() and grep(GNU)"
1853 hg diff "set:added() and grep(GNU)"
1854
1854
1855 - compare a revision and its parents::
1855 - compare a revision and its parents::
1856
1856
1857 hg diff -c 9353 # compare against first parent
1857 hg diff -c 9353 # compare against first parent
1858 hg diff -r 9353^:9353 # same using revset syntax
1858 hg diff -r 9353^:9353 # same using revset syntax
1859 hg diff -r 9353^2:9353 # compare against the second parent
1859 hg diff -r 9353^2:9353 # compare against the second parent
1860
1860
1861 Returns 0 on success.
1861 Returns 0 on success.
1862 """
1862 """
1863
1863
1864 opts = pycompat.byteskwargs(opts)
1864 opts = pycompat.byteskwargs(opts)
1865 revs = opts.get('rev')
1865 revs = opts.get('rev')
1866 change = opts.get('change')
1866 change = opts.get('change')
1867 stat = opts.get('stat')
1867 stat = opts.get('stat')
1868 reverse = opts.get('reverse')
1868 reverse = opts.get('reverse')
1869
1869
1870 if revs and change:
1870 if revs and change:
1871 msg = _('cannot specify --rev and --change at the same time')
1871 msg = _('cannot specify --rev and --change at the same time')
1872 raise error.Abort(msg)
1872 raise error.Abort(msg)
1873 elif change:
1873 elif change:
1874 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
1874 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
1875 ctx2 = scmutil.revsingle(repo, change, None)
1875 ctx2 = scmutil.revsingle(repo, change, None)
1876 ctx1 = ctx2.p1()
1876 ctx1 = ctx2.p1()
1877 else:
1877 else:
1878 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
1878 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
1879 ctx1, ctx2 = scmutil.revpair(repo, revs)
1879 ctx1, ctx2 = scmutil.revpair(repo, revs)
1880 node1, node2 = ctx1.node(), ctx2.node()
1880 node1, node2 = ctx1.node(), ctx2.node()
1881
1881
1882 if reverse:
1882 if reverse:
1883 node1, node2 = node2, node1
1883 node1, node2 = node2, node1
1884
1884
1885 diffopts = patch.diffallopts(ui, opts)
1885 diffopts = patch.diffallopts(ui, opts)
1886 m = scmutil.match(ctx2, pats, opts)
1886 m = scmutil.match(ctx2, pats, opts)
1887 ui.pager('diff')
1887 ui.pager('diff')
1888 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1888 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1889 listsubrepos=opts.get('subrepos'),
1889 listsubrepos=opts.get('subrepos'),
1890 root=opts.get('root'))
1890 root=opts.get('root'))
1891
1891
1892 @command('^export',
1892 @command('^export',
1893 [('o', 'output', '',
1893 [('o', 'output', '',
1894 _('print output to file with formatted name'), _('FORMAT')),
1894 _('print output to file with formatted name'), _('FORMAT')),
1895 ('', 'switch-parent', None, _('diff against the second parent')),
1895 ('', 'switch-parent', None, _('diff against the second parent')),
1896 ('r', 'rev', [], _('revisions to export'), _('REV')),
1896 ('r', 'rev', [], _('revisions to export'), _('REV')),
1897 ] + diffopts + formatteropts,
1897 ] + diffopts + formatteropts,
1898 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'), cmdtype=readonly)
1898 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'), cmdtype=readonly)
1899 def export(ui, repo, *changesets, **opts):
1899 def export(ui, repo, *changesets, **opts):
1900 """dump the header and diffs for one or more changesets
1900 """dump the header and diffs for one or more changesets
1901
1901
1902 Print the changeset header and diffs for one or more revisions.
1902 Print the changeset header and diffs for one or more revisions.
1903 If no revision is given, the parent of the working directory is used.
1903 If no revision is given, the parent of the working directory is used.
1904
1904
1905 The information shown in the changeset header is: author, date,
1905 The information shown in the changeset header is: author, date,
1906 branch name (if non-default), changeset hash, parent(s) and commit
1906 branch name (if non-default), changeset hash, parent(s) and commit
1907 comment.
1907 comment.
1908
1908
1909 .. note::
1909 .. note::
1910
1910
1911 :hg:`export` may generate unexpected diff output for merge
1911 :hg:`export` may generate unexpected diff output for merge
1912 changesets, as it will compare the merge changeset against its
1912 changesets, as it will compare the merge changeset against its
1913 first parent only.
1913 first parent only.
1914
1914
1915 Output may be to a file, in which case the name of the file is
1915 Output may be to a file, in which case the name of the file is
1916 given using a template string. See :hg:`help templates`. In addition
1916 given using a template string. See :hg:`help templates`. In addition
1917 to the common template keywords, the following formatting rules are
1917 to the common template keywords, the following formatting rules are
1918 supported:
1918 supported:
1919
1919
1920 :``%%``: literal "%" character
1920 :``%%``: literal "%" character
1921 :``%H``: changeset hash (40 hexadecimal digits)
1921 :``%H``: changeset hash (40 hexadecimal digits)
1922 :``%N``: number of patches being generated
1922 :``%N``: number of patches being generated
1923 :``%R``: changeset revision number
1923 :``%R``: changeset revision number
1924 :``%b``: basename of the exporting repository
1924 :``%b``: basename of the exporting repository
1925 :``%h``: short-form changeset hash (12 hexadecimal digits)
1925 :``%h``: short-form changeset hash (12 hexadecimal digits)
1926 :``%m``: first line of the commit message (only alphanumeric characters)
1926 :``%m``: first line of the commit message (only alphanumeric characters)
1927 :``%n``: zero-padded sequence number, starting at 1
1927 :``%n``: zero-padded sequence number, starting at 1
1928 :``%r``: zero-padded changeset revision number
1928 :``%r``: zero-padded changeset revision number
1929 :``\\``: literal "\\" character
1929 :``\\``: literal "\\" character
1930
1930
1931 Without the -a/--text option, export will avoid generating diffs
1931 Without the -a/--text option, export will avoid generating diffs
1932 of files it detects as binary. With -a, export will generate a
1932 of files it detects as binary. With -a, export will generate a
1933 diff anyway, probably with undesirable results.
1933 diff anyway, probably with undesirable results.
1934
1934
1935 Use the -g/--git option to generate diffs in the git extended diff
1935 Use the -g/--git option to generate diffs in the git extended diff
1936 format. See :hg:`help diffs` for more information.
1936 format. See :hg:`help diffs` for more information.
1937
1937
1938 With the --switch-parent option, the diff will be against the
1938 With the --switch-parent option, the diff will be against the
1939 second parent. It can be useful to review a merge.
1939 second parent. It can be useful to review a merge.
1940
1940
1941 .. container:: verbose
1941 .. container:: verbose
1942
1942
1943 Examples:
1943 Examples:
1944
1944
1945 - use export and import to transplant a bugfix to the current
1945 - use export and import to transplant a bugfix to the current
1946 branch::
1946 branch::
1947
1947
1948 hg export -r 9353 | hg import -
1948 hg export -r 9353 | hg import -
1949
1949
1950 - export all the changesets between two revisions to a file with
1950 - export all the changesets between two revisions to a file with
1951 rename information::
1951 rename information::
1952
1952
1953 hg export --git -r 123:150 > changes.txt
1953 hg export --git -r 123:150 > changes.txt
1954
1954
1955 - split outgoing changes into a series of patches with
1955 - split outgoing changes into a series of patches with
1956 descriptive names::
1956 descriptive names::
1957
1957
1958 hg export -r "outgoing()" -o "%n-%m.patch"
1958 hg export -r "outgoing()" -o "%n-%m.patch"
1959
1959
1960 Returns 0 on success.
1960 Returns 0 on success.
1961 """
1961 """
1962 opts = pycompat.byteskwargs(opts)
1962 opts = pycompat.byteskwargs(opts)
1963 changesets += tuple(opts.get('rev', []))
1963 changesets += tuple(opts.get('rev', []))
1964 if not changesets:
1964 if not changesets:
1965 changesets = ['.']
1965 changesets = ['.']
1966 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
1966 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
1967 revs = scmutil.revrange(repo, changesets)
1967 revs = scmutil.revrange(repo, changesets)
1968 if not revs:
1968 if not revs:
1969 raise error.Abort(_("export requires at least one changeset"))
1969 raise error.Abort(_("export requires at least one changeset"))
1970 if len(revs) > 1:
1970 if len(revs) > 1:
1971 ui.note(_('exporting patches:\n'))
1971 ui.note(_('exporting patches:\n'))
1972 else:
1972 else:
1973 ui.note(_('exporting patch:\n'))
1973 ui.note(_('exporting patch:\n'))
1974
1974
1975 fntemplate = opts.get('output')
1975 fntemplate = opts.get('output')
1976 if cmdutil.isstdiofilename(fntemplate):
1976 if cmdutil.isstdiofilename(fntemplate):
1977 fntemplate = ''
1977 fntemplate = ''
1978
1978
1979 if fntemplate:
1979 if fntemplate:
1980 fm = formatter.nullformatter(ui, 'export', opts)
1980 fm = formatter.nullformatter(ui, 'export', opts)
1981 else:
1981 else:
1982 ui.pager('export')
1982 ui.pager('export')
1983 fm = ui.formatter('export', opts)
1983 fm = ui.formatter('export', opts)
1984 with fm:
1984 with fm:
1985 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
1985 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
1986 switch_parent=opts.get('switch_parent'),
1986 switch_parent=opts.get('switch_parent'),
1987 opts=patch.diffallopts(ui, opts))
1987 opts=patch.diffallopts(ui, opts))
1988
1988
1989 @command('files',
1989 @command('files',
1990 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
1990 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
1991 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
1991 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
1992 ] + walkopts + formatteropts + subrepoopts,
1992 ] + walkopts + formatteropts + subrepoopts,
1993 _('[OPTION]... [FILE]...'), cmdtype=readonly)
1993 _('[OPTION]... [FILE]...'), cmdtype=readonly)
1994 def files(ui, repo, *pats, **opts):
1994 def files(ui, repo, *pats, **opts):
1995 """list tracked files
1995 """list tracked files
1996
1996
1997 Print files under Mercurial control in the working directory or
1997 Print files under Mercurial control in the working directory or
1998 specified revision for given files (excluding removed files).
1998 specified revision for given files (excluding removed files).
1999 Files can be specified as filenames or filesets.
1999 Files can be specified as filenames or filesets.
2000
2000
2001 If no files are given to match, this command prints the names
2001 If no files are given to match, this command prints the names
2002 of all files under Mercurial control.
2002 of all files under Mercurial control.
2003
2003
2004 .. container:: verbose
2004 .. container:: verbose
2005
2005
2006 Examples:
2006 Examples:
2007
2007
2008 - list all files under the current directory::
2008 - list all files under the current directory::
2009
2009
2010 hg files .
2010 hg files .
2011
2011
2012 - shows sizes and flags for current revision::
2012 - shows sizes and flags for current revision::
2013
2013
2014 hg files -vr .
2014 hg files -vr .
2015
2015
2016 - list all files named README::
2016 - list all files named README::
2017
2017
2018 hg files -I "**/README"
2018 hg files -I "**/README"
2019
2019
2020 - list all binary files::
2020 - list all binary files::
2021
2021
2022 hg files "set:binary()"
2022 hg files "set:binary()"
2023
2023
2024 - find files containing a regular expression::
2024 - find files containing a regular expression::
2025
2025
2026 hg files "set:grep('bob')"
2026 hg files "set:grep('bob')"
2027
2027
2028 - search tracked file contents with xargs and grep::
2028 - search tracked file contents with xargs and grep::
2029
2029
2030 hg files -0 | xargs -0 grep foo
2030 hg files -0 | xargs -0 grep foo
2031
2031
2032 See :hg:`help patterns` and :hg:`help filesets` for more information
2032 See :hg:`help patterns` and :hg:`help filesets` for more information
2033 on specifying file patterns.
2033 on specifying file patterns.
2034
2034
2035 Returns 0 if a match is found, 1 otherwise.
2035 Returns 0 if a match is found, 1 otherwise.
2036
2036
2037 """
2037 """
2038
2038
2039 opts = pycompat.byteskwargs(opts)
2039 opts = pycompat.byteskwargs(opts)
2040 rev = opts.get('rev')
2040 rev = opts.get('rev')
2041 if rev:
2041 if rev:
2042 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2042 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2043 ctx = scmutil.revsingle(repo, rev, None)
2043 ctx = scmutil.revsingle(repo, rev, None)
2044
2044
2045 end = '\n'
2045 end = '\n'
2046 if opts.get('print0'):
2046 if opts.get('print0'):
2047 end = '\0'
2047 end = '\0'
2048 fmt = '%s' + end
2048 fmt = '%s' + end
2049
2049
2050 m = scmutil.match(ctx, pats, opts)
2050 m = scmutil.match(ctx, pats, opts)
2051 ui.pager('files')
2051 ui.pager('files')
2052 with ui.formatter('files', opts) as fm:
2052 with ui.formatter('files', opts) as fm:
2053 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2053 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2054
2054
2055 @command(
2055 @command(
2056 '^forget',
2056 '^forget',
2057 walkopts + dryrunopts,
2057 walkopts + dryrunopts,
2058 _('[OPTION]... FILE...'), inferrepo=True)
2058 _('[OPTION]... FILE...'), inferrepo=True)
2059 def forget(ui, repo, *pats, **opts):
2059 def forget(ui, repo, *pats, **opts):
2060 """forget the specified files on the next commit
2060 """forget the specified files on the next commit
2061
2061
2062 Mark the specified files so they will no longer be tracked
2062 Mark the specified files so they will no longer be tracked
2063 after the next commit.
2063 after the next commit.
2064
2064
2065 This only removes files from the current branch, not from the
2065 This only removes files from the current branch, not from the
2066 entire project history, and it does not delete them from the
2066 entire project history, and it does not delete them from the
2067 working directory.
2067 working directory.
2068
2068
2069 To delete the file from the working directory, see :hg:`remove`.
2069 To delete the file from the working directory, see :hg:`remove`.
2070
2070
2071 To undo a forget before the next commit, see :hg:`add`.
2071 To undo a forget before the next commit, see :hg:`add`.
2072
2072
2073 .. container:: verbose
2073 .. container:: verbose
2074
2074
2075 Examples:
2075 Examples:
2076
2076
2077 - forget newly-added binary files::
2077 - forget newly-added binary files::
2078
2078
2079 hg forget "set:added() and binary()"
2079 hg forget "set:added() and binary()"
2080
2080
2081 - forget files that would be excluded by .hgignore::
2081 - forget files that would be excluded by .hgignore::
2082
2082
2083 hg forget "set:hgignore()"
2083 hg forget "set:hgignore()"
2084
2084
2085 Returns 0 on success.
2085 Returns 0 on success.
2086 """
2086 """
2087
2087
2088 opts = pycompat.byteskwargs(opts)
2088 opts = pycompat.byteskwargs(opts)
2089 if not pats:
2089 if not pats:
2090 raise error.Abort(_('no files specified'))
2090 raise error.Abort(_('no files specified'))
2091
2091
2092 m = scmutil.match(repo[None], pats, opts)
2092 m = scmutil.match(repo[None], pats, opts)
2093 dryrun = opts.get(r'dry_run')
2093 dryrun = opts.get(r'dry_run')
2094 rejected = cmdutil.forget(ui, repo, m, prefix="",
2094 rejected = cmdutil.forget(ui, repo, m, prefix="",
2095 explicitonly=False, dryrun=dryrun)[0]
2095 explicitonly=False, dryrun=dryrun)[0]
2096 return rejected and 1 or 0
2096 return rejected and 1 or 0
2097
2097
2098 @command(
2098 @command(
2099 'graft',
2099 'graft',
2100 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2100 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2101 ('c', 'continue', False, _('resume interrupted graft')),
2101 ('c', 'continue', False, _('resume interrupted graft')),
2102 ('e', 'edit', False, _('invoke editor on commit messages')),
2102 ('e', 'edit', False, _('invoke editor on commit messages')),
2103 ('', 'log', None, _('append graft info to log message')),
2103 ('', 'log', None, _('append graft info to log message')),
2104 ('f', 'force', False, _('force graft')),
2104 ('f', 'force', False, _('force graft')),
2105 ('D', 'currentdate', False,
2105 ('D', 'currentdate', False,
2106 _('record the current date as commit date')),
2106 _('record the current date as commit date')),
2107 ('U', 'currentuser', False,
2107 ('U', 'currentuser', False,
2108 _('record the current user as committer'), _('DATE'))]
2108 _('record the current user as committer'), _('DATE'))]
2109 + commitopts2 + mergetoolopts + dryrunopts,
2109 + commitopts2 + mergetoolopts + dryrunopts,
2110 _('[OPTION]... [-r REV]... REV...'))
2110 _('[OPTION]... [-r REV]... REV...'))
2111 def graft(ui, repo, *revs, **opts):
2111 def graft(ui, repo, *revs, **opts):
2112 '''copy changes from other branches onto the current branch
2112 '''copy changes from other branches onto the current branch
2113
2113
2114 This command uses Mercurial's merge logic to copy individual
2114 This command uses Mercurial's merge logic to copy individual
2115 changes from other branches without merging branches in the
2115 changes from other branches without merging branches in the
2116 history graph. This is sometimes known as 'backporting' or
2116 history graph. This is sometimes known as 'backporting' or
2117 'cherry-picking'. By default, graft will copy user, date, and
2117 'cherry-picking'. By default, graft will copy user, date, and
2118 description from the source changesets.
2118 description from the source changesets.
2119
2119
2120 Changesets that are ancestors of the current revision, that have
2120 Changesets that are ancestors of the current revision, that have
2121 already been grafted, or that are merges will be skipped.
2121 already been grafted, or that are merges will be skipped.
2122
2122
2123 If --log is specified, log messages will have a comment appended
2123 If --log is specified, log messages will have a comment appended
2124 of the form::
2124 of the form::
2125
2125
2126 (grafted from CHANGESETHASH)
2126 (grafted from CHANGESETHASH)
2127
2127
2128 If --force is specified, revisions will be grafted even if they
2128 If --force is specified, revisions will be grafted even if they
2129 are already ancestors of, or have been grafted to, the destination.
2129 are already ancestors of, or have been grafted to, the destination.
2130 This is useful when the revisions have since been backed out.
2130 This is useful when the revisions have since been backed out.
2131
2131
2132 If a graft merge results in conflicts, the graft process is
2132 If a graft merge results in conflicts, the graft process is
2133 interrupted so that the current merge can be manually resolved.
2133 interrupted so that the current merge can be manually resolved.
2134 Once all conflicts are addressed, the graft process can be
2134 Once all conflicts are addressed, the graft process can be
2135 continued with the -c/--continue option.
2135 continued with the -c/--continue option.
2136
2136
2137 .. note::
2137 .. note::
2138
2138
2139 The -c/--continue option does not reapply earlier options, except
2139 The -c/--continue option does not reapply earlier options, except
2140 for --force.
2140 for --force.
2141
2141
2142 .. container:: verbose
2142 .. container:: verbose
2143
2143
2144 Examples:
2144 Examples:
2145
2145
2146 - copy a single change to the stable branch and edit its description::
2146 - copy a single change to the stable branch and edit its description::
2147
2147
2148 hg update stable
2148 hg update stable
2149 hg graft --edit 9393
2149 hg graft --edit 9393
2150
2150
2151 - graft a range of changesets with one exception, updating dates::
2151 - graft a range of changesets with one exception, updating dates::
2152
2152
2153 hg graft -D "2085::2093 and not 2091"
2153 hg graft -D "2085::2093 and not 2091"
2154
2154
2155 - continue a graft after resolving conflicts::
2155 - continue a graft after resolving conflicts::
2156
2156
2157 hg graft -c
2157 hg graft -c
2158
2158
2159 - show the source of a grafted changeset::
2159 - show the source of a grafted changeset::
2160
2160
2161 hg log --debug -r .
2161 hg log --debug -r .
2162
2162
2163 - show revisions sorted by date::
2163 - show revisions sorted by date::
2164
2164
2165 hg log -r "sort(all(), date)"
2165 hg log -r "sort(all(), date)"
2166
2166
2167 See :hg:`help revisions` for more about specifying revisions.
2167 See :hg:`help revisions` for more about specifying revisions.
2168
2168
2169 Returns 0 on successful completion.
2169 Returns 0 on successful completion.
2170 '''
2170 '''
2171 with repo.wlock():
2171 with repo.wlock():
2172 return _dograft(ui, repo, *revs, **opts)
2172 return _dograft(ui, repo, *revs, **opts)
2173
2173
2174 def _dograft(ui, repo, *revs, **opts):
2174 def _dograft(ui, repo, *revs, **opts):
2175 opts = pycompat.byteskwargs(opts)
2175 opts = pycompat.byteskwargs(opts)
2176 if revs and opts.get('rev'):
2176 if revs and opts.get('rev'):
2177 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2177 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2178 'revision ordering!\n'))
2178 'revision ordering!\n'))
2179
2179
2180 revs = list(revs)
2180 revs = list(revs)
2181 revs.extend(opts.get('rev'))
2181 revs.extend(opts.get('rev'))
2182
2182
2183 if not opts.get('user') and opts.get('currentuser'):
2183 if not opts.get('user') and opts.get('currentuser'):
2184 opts['user'] = ui.username()
2184 opts['user'] = ui.username()
2185 if not opts.get('date') and opts.get('currentdate'):
2185 if not opts.get('date') and opts.get('currentdate'):
2186 opts['date'] = "%d %d" % dateutil.makedate()
2186 opts['date'] = "%d %d" % dateutil.makedate()
2187
2187
2188 editor = cmdutil.getcommiteditor(editform='graft',
2188 editor = cmdutil.getcommiteditor(editform='graft',
2189 **pycompat.strkwargs(opts))
2189 **pycompat.strkwargs(opts))
2190
2190
2191 cont = False
2191 cont = False
2192 if opts.get('continue'):
2192 if opts.get('continue'):
2193 cont = True
2193 cont = True
2194 if revs:
2194 if revs:
2195 raise error.Abort(_("can't specify --continue and revisions"))
2195 raise error.Abort(_("can't specify --continue and revisions"))
2196 # read in unfinished revisions
2196 # read in unfinished revisions
2197 try:
2197 try:
2198 nodes = repo.vfs.read('graftstate').splitlines()
2198 nodes = repo.vfs.read('graftstate').splitlines()
2199 revs = [repo[node].rev() for node in nodes]
2199 revs = [repo[node].rev() for node in nodes]
2200 except IOError as inst:
2200 except IOError as inst:
2201 if inst.errno != errno.ENOENT:
2201 if inst.errno != errno.ENOENT:
2202 raise
2202 raise
2203 cmdutil.wrongtooltocontinue(repo, _('graft'))
2203 cmdutil.wrongtooltocontinue(repo, _('graft'))
2204 else:
2204 else:
2205 if not revs:
2205 if not revs:
2206 raise error.Abort(_('no revisions specified'))
2206 raise error.Abort(_('no revisions specified'))
2207 cmdutil.checkunfinished(repo)
2207 cmdutil.checkunfinished(repo)
2208 cmdutil.bailifchanged(repo)
2208 cmdutil.bailifchanged(repo)
2209 revs = scmutil.revrange(repo, revs)
2209 revs = scmutil.revrange(repo, revs)
2210
2210
2211 skipped = set()
2211 skipped = set()
2212 # check for merges
2212 # check for merges
2213 for rev in repo.revs('%ld and merge()', revs):
2213 for rev in repo.revs('%ld and merge()', revs):
2214 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2214 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2215 skipped.add(rev)
2215 skipped.add(rev)
2216 revs = [r for r in revs if r not in skipped]
2216 revs = [r for r in revs if r not in skipped]
2217 if not revs:
2217 if not revs:
2218 return -1
2218 return -1
2219
2219
2220 # Don't check in the --continue case, in effect retaining --force across
2220 # Don't check in the --continue case, in effect retaining --force across
2221 # --continues. That's because without --force, any revisions we decided to
2221 # --continues. That's because without --force, any revisions we decided to
2222 # skip would have been filtered out here, so they wouldn't have made their
2222 # skip would have been filtered out here, so they wouldn't have made their
2223 # way to the graftstate. With --force, any revisions we would have otherwise
2223 # way to the graftstate. With --force, any revisions we would have otherwise
2224 # skipped would not have been filtered out, and if they hadn't been applied
2224 # skipped would not have been filtered out, and if they hadn't been applied
2225 # already, they'd have been in the graftstate.
2225 # already, they'd have been in the graftstate.
2226 if not (cont or opts.get('force')):
2226 if not (cont or opts.get('force')):
2227 # check for ancestors of dest branch
2227 # check for ancestors of dest branch
2228 crev = repo['.'].rev()
2228 crev = repo['.'].rev()
2229 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2229 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2230 # XXX make this lazy in the future
2230 # XXX make this lazy in the future
2231 # don't mutate while iterating, create a copy
2231 # don't mutate while iterating, create a copy
2232 for rev in list(revs):
2232 for rev in list(revs):
2233 if rev in ancestors:
2233 if rev in ancestors:
2234 ui.warn(_('skipping ancestor revision %d:%s\n') %
2234 ui.warn(_('skipping ancestor revision %d:%s\n') %
2235 (rev, repo[rev]))
2235 (rev, repo[rev]))
2236 # XXX remove on list is slow
2236 # XXX remove on list is slow
2237 revs.remove(rev)
2237 revs.remove(rev)
2238 if not revs:
2238 if not revs:
2239 return -1
2239 return -1
2240
2240
2241 # analyze revs for earlier grafts
2241 # analyze revs for earlier grafts
2242 ids = {}
2242 ids = {}
2243 for ctx in repo.set("%ld", revs):
2243 for ctx in repo.set("%ld", revs):
2244 ids[ctx.hex()] = ctx.rev()
2244 ids[ctx.hex()] = ctx.rev()
2245 n = ctx.extra().get('source')
2245 n = ctx.extra().get('source')
2246 if n:
2246 if n:
2247 ids[n] = ctx.rev()
2247 ids[n] = ctx.rev()
2248
2248
2249 # check ancestors for earlier grafts
2249 # check ancestors for earlier grafts
2250 ui.debug('scanning for duplicate grafts\n')
2250 ui.debug('scanning for duplicate grafts\n')
2251
2251
2252 # The only changesets we can be sure doesn't contain grafts of any
2252 # The only changesets we can be sure doesn't contain grafts of any
2253 # revs, are the ones that are common ancestors of *all* revs:
2253 # revs, are the ones that are common ancestors of *all* revs:
2254 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2254 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2255 ctx = repo[rev]
2255 ctx = repo[rev]
2256 n = ctx.extra().get('source')
2256 n = ctx.extra().get('source')
2257 if n in ids:
2257 if n in ids:
2258 try:
2258 try:
2259 r = repo[n].rev()
2259 r = repo[n].rev()
2260 except error.RepoLookupError:
2260 except error.RepoLookupError:
2261 r = None
2261 r = None
2262 if r in revs:
2262 if r in revs:
2263 ui.warn(_('skipping revision %d:%s '
2263 ui.warn(_('skipping revision %d:%s '
2264 '(already grafted to %d:%s)\n')
2264 '(already grafted to %d:%s)\n')
2265 % (r, repo[r], rev, ctx))
2265 % (r, repo[r], rev, ctx))
2266 revs.remove(r)
2266 revs.remove(r)
2267 elif ids[n] in revs:
2267 elif ids[n] in revs:
2268 if r is None:
2268 if r is None:
2269 ui.warn(_('skipping already grafted revision %d:%s '
2269 ui.warn(_('skipping already grafted revision %d:%s '
2270 '(%d:%s also has unknown origin %s)\n')
2270 '(%d:%s also has unknown origin %s)\n')
2271 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2271 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2272 else:
2272 else:
2273 ui.warn(_('skipping already grafted revision %d:%s '
2273 ui.warn(_('skipping already grafted revision %d:%s '
2274 '(%d:%s also has origin %d:%s)\n')
2274 '(%d:%s also has origin %d:%s)\n')
2275 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2275 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2276 revs.remove(ids[n])
2276 revs.remove(ids[n])
2277 elif ctx.hex() in ids:
2277 elif ctx.hex() in ids:
2278 r = ids[ctx.hex()]
2278 r = ids[ctx.hex()]
2279 ui.warn(_('skipping already grafted revision %d:%s '
2279 ui.warn(_('skipping already grafted revision %d:%s '
2280 '(was grafted from %d:%s)\n') %
2280 '(was grafted from %d:%s)\n') %
2281 (r, repo[r], rev, ctx))
2281 (r, repo[r], rev, ctx))
2282 revs.remove(r)
2282 revs.remove(r)
2283 if not revs:
2283 if not revs:
2284 return -1
2284 return -1
2285
2285
2286 for pos, ctx in enumerate(repo.set("%ld", revs)):
2286 for pos, ctx in enumerate(repo.set("%ld", revs)):
2287 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2287 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2288 ctx.description().split('\n', 1)[0])
2288 ctx.description().split('\n', 1)[0])
2289 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2289 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2290 if names:
2290 if names:
2291 desc += ' (%s)' % ' '.join(names)
2291 desc += ' (%s)' % ' '.join(names)
2292 ui.status(_('grafting %s\n') % desc)
2292 ui.status(_('grafting %s\n') % desc)
2293 if opts.get('dry_run'):
2293 if opts.get('dry_run'):
2294 continue
2294 continue
2295
2295
2296 source = ctx.extra().get('source')
2296 source = ctx.extra().get('source')
2297 extra = {}
2297 extra = {}
2298 if source:
2298 if source:
2299 extra['source'] = source
2299 extra['source'] = source
2300 extra['intermediate-source'] = ctx.hex()
2300 extra['intermediate-source'] = ctx.hex()
2301 else:
2301 else:
2302 extra['source'] = ctx.hex()
2302 extra['source'] = ctx.hex()
2303 user = ctx.user()
2303 user = ctx.user()
2304 if opts.get('user'):
2304 if opts.get('user'):
2305 user = opts['user']
2305 user = opts['user']
2306 date = ctx.date()
2306 date = ctx.date()
2307 if opts.get('date'):
2307 if opts.get('date'):
2308 date = opts['date']
2308 date = opts['date']
2309 message = ctx.description()
2309 message = ctx.description()
2310 if opts.get('log'):
2310 if opts.get('log'):
2311 message += '\n(grafted from %s)' % ctx.hex()
2311 message += '\n(grafted from %s)' % ctx.hex()
2312
2312
2313 # we don't merge the first commit when continuing
2313 # we don't merge the first commit when continuing
2314 if not cont:
2314 if not cont:
2315 # perform the graft merge with p1(rev) as 'ancestor'
2315 # perform the graft merge with p1(rev) as 'ancestor'
2316 try:
2316 try:
2317 # ui.forcemerge is an internal variable, do not document
2317 # ui.forcemerge is an internal variable, do not document
2318 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
2318 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
2319 'graft')
2319 'graft')
2320 stats = mergemod.graft(repo, ctx, ctx.p1(),
2320 stats = mergemod.graft(repo, ctx, ctx.p1(),
2321 ['local', 'graft'])
2321 ['local', 'graft'])
2322 finally:
2322 finally:
2323 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
2323 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
2324 # report any conflicts
2324 # report any conflicts
2325 if stats.unresolvedcount > 0:
2325 if stats.unresolvedcount > 0:
2326 # write out state for --continue
2326 # write out state for --continue
2327 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2327 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2328 repo.vfs.write('graftstate', ''.join(nodelines))
2328 repo.vfs.write('graftstate', ''.join(nodelines))
2329 extra = ''
2329 extra = ''
2330 if opts.get('user'):
2330 if opts.get('user'):
2331 extra += ' --user %s' % procutil.shellquote(opts['user'])
2331 extra += ' --user %s' % procutil.shellquote(opts['user'])
2332 if opts.get('date'):
2332 if opts.get('date'):
2333 extra += ' --date %s' % procutil.shellquote(opts['date'])
2333 extra += ' --date %s' % procutil.shellquote(opts['date'])
2334 if opts.get('log'):
2334 if opts.get('log'):
2335 extra += ' --log'
2335 extra += ' --log'
2336 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
2336 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
2337 raise error.Abort(
2337 raise error.Abort(
2338 _("unresolved conflicts, can't continue"),
2338 _("unresolved conflicts, can't continue"),
2339 hint=hint)
2339 hint=hint)
2340 else:
2340 else:
2341 cont = False
2341 cont = False
2342
2342
2343 # commit
2343 # commit
2344 node = repo.commit(text=message, user=user,
2344 node = repo.commit(text=message, user=user,
2345 date=date, extra=extra, editor=editor)
2345 date=date, extra=extra, editor=editor)
2346 if node is None:
2346 if node is None:
2347 ui.warn(
2347 ui.warn(
2348 _('note: graft of %d:%s created no changes to commit\n') %
2348 _('note: graft of %d:%s created no changes to commit\n') %
2349 (ctx.rev(), ctx))
2349 (ctx.rev(), ctx))
2350
2350
2351 # remove state when we complete successfully
2351 # remove state when we complete successfully
2352 if not opts.get('dry_run'):
2352 if not opts.get('dry_run'):
2353 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
2353 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
2354
2354
2355 return 0
2355 return 0
2356
2356
2357 @command('grep',
2357 @command('grep',
2358 [('0', 'print0', None, _('end fields with NUL')),
2358 [('0', 'print0', None, _('end fields with NUL')),
2359 ('', 'all', None, _('print all revisions that match')),
2359 ('', 'all', None, _('print all revisions that match')),
2360 ('a', 'text', None, _('treat all files as text')),
2360 ('a', 'text', None, _('treat all files as text')),
2361 ('f', 'follow', None,
2361 ('f', 'follow', None,
2362 _('follow changeset history,'
2362 _('follow changeset history,'
2363 ' or file history across copies and renames')),
2363 ' or file history across copies and renames')),
2364 ('i', 'ignore-case', None, _('ignore case when matching')),
2364 ('i', 'ignore-case', None, _('ignore case when matching')),
2365 ('l', 'files-with-matches', None,
2365 ('l', 'files-with-matches', None,
2366 _('print only filenames and revisions that match')),
2366 _('print only filenames and revisions that match')),
2367 ('n', 'line-number', None, _('print matching line numbers')),
2367 ('n', 'line-number', None, _('print matching line numbers')),
2368 ('r', 'rev', [],
2368 ('r', 'rev', [],
2369 _('only search files changed within revision range'), _('REV')),
2369 _('only search files changed within revision range'), _('REV')),
2370 ('u', 'user', None, _('list the author (long with -v)')),
2370 ('u', 'user', None, _('list the author (long with -v)')),
2371 ('d', 'date', None, _('list the date (short with -q)')),
2371 ('d', 'date', None, _('list the date (short with -q)')),
2372 ] + formatteropts + walkopts,
2372 ] + formatteropts + walkopts,
2373 _('[OPTION]... PATTERN [FILE]...'),
2373 _('[OPTION]... PATTERN [FILE]...'),
2374 inferrepo=True, cmdtype=readonly)
2374 inferrepo=True, cmdtype=readonly)
2375 def grep(ui, repo, pattern, *pats, **opts):
2375 def grep(ui, repo, pattern, *pats, **opts):
2376 """search revision history for a pattern in specified files
2376 """search revision history for a pattern in specified files
2377
2377
2378 Search revision history for a regular expression in the specified
2378 Search revision history for a regular expression in the specified
2379 files or the entire project.
2379 files or the entire project.
2380
2380
2381 By default, grep prints the most recent revision number for each
2381 By default, grep prints the most recent revision number for each
2382 file in which it finds a match. To get it to print every revision
2382 file in which it finds a match. To get it to print every revision
2383 that contains a change in match status ("-" for a match that becomes
2383 that contains a change in match status ("-" for a match that becomes
2384 a non-match, or "+" for a non-match that becomes a match), use the
2384 a non-match, or "+" for a non-match that becomes a match), use the
2385 --all flag.
2385 --all flag.
2386
2386
2387 PATTERN can be any Python (roughly Perl-compatible) regular
2387 PATTERN can be any Python (roughly Perl-compatible) regular
2388 expression.
2388 expression.
2389
2389
2390 If no FILEs are specified (and -f/--follow isn't set), all files in
2390 If no FILEs are specified (and -f/--follow isn't set), all files in
2391 the repository are searched, including those that don't exist in the
2391 the repository are searched, including those that don't exist in the
2392 current branch or have been deleted in a prior changeset.
2392 current branch or have been deleted in a prior changeset.
2393
2393
2394 Returns 0 if a match is found, 1 otherwise.
2394 Returns 0 if a match is found, 1 otherwise.
2395 """
2395 """
2396 opts = pycompat.byteskwargs(opts)
2396 opts = pycompat.byteskwargs(opts)
2397 reflags = re.M
2397 reflags = re.M
2398 if opts.get('ignore_case'):
2398 if opts.get('ignore_case'):
2399 reflags |= re.I
2399 reflags |= re.I
2400 try:
2400 try:
2401 regexp = util.re.compile(pattern, reflags)
2401 regexp = util.re.compile(pattern, reflags)
2402 except re.error as inst:
2402 except re.error as inst:
2403 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2403 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2404 return 1
2404 return 1
2405 sep, eol = ':', '\n'
2405 sep, eol = ':', '\n'
2406 if opts.get('print0'):
2406 if opts.get('print0'):
2407 sep = eol = '\0'
2407 sep = eol = '\0'
2408
2408
2409 getfile = util.lrucachefunc(repo.file)
2409 getfile = util.lrucachefunc(repo.file)
2410
2410
2411 def matchlines(body):
2411 def matchlines(body):
2412 begin = 0
2412 begin = 0
2413 linenum = 0
2413 linenum = 0
2414 while begin < len(body):
2414 while begin < len(body):
2415 match = regexp.search(body, begin)
2415 match = regexp.search(body, begin)
2416 if not match:
2416 if not match:
2417 break
2417 break
2418 mstart, mend = match.span()
2418 mstart, mend = match.span()
2419 linenum += body.count('\n', begin, mstart) + 1
2419 linenum += body.count('\n', begin, mstart) + 1
2420 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2420 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2421 begin = body.find('\n', mend) + 1 or len(body) + 1
2421 begin = body.find('\n', mend) + 1 or len(body) + 1
2422 lend = begin - 1
2422 lend = begin - 1
2423 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2423 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2424
2424
2425 class linestate(object):
2425 class linestate(object):
2426 def __init__(self, line, linenum, colstart, colend):
2426 def __init__(self, line, linenum, colstart, colend):
2427 self.line = line
2427 self.line = line
2428 self.linenum = linenum
2428 self.linenum = linenum
2429 self.colstart = colstart
2429 self.colstart = colstart
2430 self.colend = colend
2430 self.colend = colend
2431
2431
2432 def __hash__(self):
2432 def __hash__(self):
2433 return hash((self.linenum, self.line))
2433 return hash((self.linenum, self.line))
2434
2434
2435 def __eq__(self, other):
2435 def __eq__(self, other):
2436 return self.line == other.line
2436 return self.line == other.line
2437
2437
2438 def findpos(self):
2438 def findpos(self):
2439 """Iterate all (start, end) indices of matches"""
2439 """Iterate all (start, end) indices of matches"""
2440 yield self.colstart, self.colend
2440 yield self.colstart, self.colend
2441 p = self.colend
2441 p = self.colend
2442 while p < len(self.line):
2442 while p < len(self.line):
2443 m = regexp.search(self.line, p)
2443 m = regexp.search(self.line, p)
2444 if not m:
2444 if not m:
2445 break
2445 break
2446 yield m.span()
2446 yield m.span()
2447 p = m.end()
2447 p = m.end()
2448
2448
2449 matches = {}
2449 matches = {}
2450 copies = {}
2450 copies = {}
2451 def grepbody(fn, rev, body):
2451 def grepbody(fn, rev, body):
2452 matches[rev].setdefault(fn, [])
2452 matches[rev].setdefault(fn, [])
2453 m = matches[rev][fn]
2453 m = matches[rev][fn]
2454 for lnum, cstart, cend, line in matchlines(body):
2454 for lnum, cstart, cend, line in matchlines(body):
2455 s = linestate(line, lnum, cstart, cend)
2455 s = linestate(line, lnum, cstart, cend)
2456 m.append(s)
2456 m.append(s)
2457
2457
2458 def difflinestates(a, b):
2458 def difflinestates(a, b):
2459 sm = difflib.SequenceMatcher(None, a, b)
2459 sm = difflib.SequenceMatcher(None, a, b)
2460 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2460 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2461 if tag == 'insert':
2461 if tag == 'insert':
2462 for i in xrange(blo, bhi):
2462 for i in xrange(blo, bhi):
2463 yield ('+', b[i])
2463 yield ('+', b[i])
2464 elif tag == 'delete':
2464 elif tag == 'delete':
2465 for i in xrange(alo, ahi):
2465 for i in xrange(alo, ahi):
2466 yield ('-', a[i])
2466 yield ('-', a[i])
2467 elif tag == 'replace':
2467 elif tag == 'replace':
2468 for i in xrange(alo, ahi):
2468 for i in xrange(alo, ahi):
2469 yield ('-', a[i])
2469 yield ('-', a[i])
2470 for i in xrange(blo, bhi):
2470 for i in xrange(blo, bhi):
2471 yield ('+', b[i])
2471 yield ('+', b[i])
2472
2472
2473 def display(fm, fn, ctx, pstates, states):
2473 def display(fm, fn, ctx, pstates, states):
2474 rev = ctx.rev()
2474 rev = ctx.rev()
2475 if fm.isplain():
2475 if fm.isplain():
2476 formatuser = ui.shortuser
2476 formatuser = ui.shortuser
2477 else:
2477 else:
2478 formatuser = str
2478 formatuser = str
2479 if ui.quiet:
2479 if ui.quiet:
2480 datefmt = '%Y-%m-%d'
2480 datefmt = '%Y-%m-%d'
2481 else:
2481 else:
2482 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2482 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2483 found = False
2483 found = False
2484 @util.cachefunc
2484 @util.cachefunc
2485 def binary():
2485 def binary():
2486 flog = getfile(fn)
2486 flog = getfile(fn)
2487 return stringutil.binary(flog.read(ctx.filenode(fn)))
2487 return stringutil.binary(flog.read(ctx.filenode(fn)))
2488
2488
2489 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2489 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2490 if opts.get('all'):
2490 if opts.get('all'):
2491 iter = difflinestates(pstates, states)
2491 iter = difflinestates(pstates, states)
2492 else:
2492 else:
2493 iter = [('', l) for l in states]
2493 iter = [('', l) for l in states]
2494 for change, l in iter:
2494 for change, l in iter:
2495 fm.startitem()
2495 fm.startitem()
2496 fm.data(node=fm.hexfunc(ctx.node()))
2496 fm.data(node=fm.hexfunc(ctx.node()))
2497 cols = [
2497 cols = [
2498 ('filename', fn, True),
2498 ('filename', fn, True),
2499 ('rev', rev, True),
2499 ('rev', rev, True),
2500 ('linenumber', l.linenum, opts.get('line_number')),
2500 ('linenumber', l.linenum, opts.get('line_number')),
2501 ]
2501 ]
2502 if opts.get('all'):
2502 if opts.get('all'):
2503 cols.append(('change', change, True))
2503 cols.append(('change', change, True))
2504 cols.extend([
2504 cols.extend([
2505 ('user', formatuser(ctx.user()), opts.get('user')),
2505 ('user', formatuser(ctx.user()), opts.get('user')),
2506 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2506 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2507 ])
2507 ])
2508 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2508 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2509 for name, data, cond in cols:
2509 for name, data, cond in cols:
2510 field = fieldnamemap.get(name, name)
2510 field = fieldnamemap.get(name, name)
2511 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2511 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2512 if cond and name != lastcol:
2512 if cond and name != lastcol:
2513 fm.plain(sep, label='grep.sep')
2513 fm.plain(sep, label='grep.sep')
2514 if not opts.get('files_with_matches'):
2514 if not opts.get('files_with_matches'):
2515 fm.plain(sep, label='grep.sep')
2515 fm.plain(sep, label='grep.sep')
2516 if not opts.get('text') and binary():
2516 if not opts.get('text') and binary():
2517 fm.plain(_(" Binary file matches"))
2517 fm.plain(_(" Binary file matches"))
2518 else:
2518 else:
2519 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2519 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2520 fm.plain(eol)
2520 fm.plain(eol)
2521 found = True
2521 found = True
2522 if opts.get('files_with_matches'):
2522 if opts.get('files_with_matches'):
2523 break
2523 break
2524 return found
2524 return found
2525
2525
2526 def displaymatches(fm, l):
2526 def displaymatches(fm, l):
2527 p = 0
2527 p = 0
2528 for s, e in l.findpos():
2528 for s, e in l.findpos():
2529 if p < s:
2529 if p < s:
2530 fm.startitem()
2530 fm.startitem()
2531 fm.write('text', '%s', l.line[p:s])
2531 fm.write('text', '%s', l.line[p:s])
2532 fm.data(matched=False)
2532 fm.data(matched=False)
2533 fm.startitem()
2533 fm.startitem()
2534 fm.write('text', '%s', l.line[s:e], label='grep.match')
2534 fm.write('text', '%s', l.line[s:e], label='grep.match')
2535 fm.data(matched=True)
2535 fm.data(matched=True)
2536 p = e
2536 p = e
2537 if p < len(l.line):
2537 if p < len(l.line):
2538 fm.startitem()
2538 fm.startitem()
2539 fm.write('text', '%s', l.line[p:])
2539 fm.write('text', '%s', l.line[p:])
2540 fm.data(matched=False)
2540 fm.data(matched=False)
2541 fm.end()
2541 fm.end()
2542
2542
2543 skip = {}
2543 skip = {}
2544 revfiles = {}
2544 revfiles = {}
2545 match = scmutil.match(repo[None], pats, opts)
2545 match = scmutil.match(repo[None], pats, opts)
2546 found = False
2546 found = False
2547 follow = opts.get('follow')
2547 follow = opts.get('follow')
2548
2548
2549 def prep(ctx, fns):
2549 def prep(ctx, fns):
2550 rev = ctx.rev()
2550 rev = ctx.rev()
2551 pctx = ctx.p1()
2551 pctx = ctx.p1()
2552 parent = pctx.rev()
2552 parent = pctx.rev()
2553 matches.setdefault(rev, {})
2553 matches.setdefault(rev, {})
2554 matches.setdefault(parent, {})
2554 matches.setdefault(parent, {})
2555 files = revfiles.setdefault(rev, [])
2555 files = revfiles.setdefault(rev, [])
2556 for fn in fns:
2556 for fn in fns:
2557 flog = getfile(fn)
2557 flog = getfile(fn)
2558 try:
2558 try:
2559 fnode = ctx.filenode(fn)
2559 fnode = ctx.filenode(fn)
2560 except error.LookupError:
2560 except error.LookupError:
2561 continue
2561 continue
2562
2562
2563 copied = flog.renamed(fnode)
2563 copied = flog.renamed(fnode)
2564 copy = follow and copied and copied[0]
2564 copy = follow and copied and copied[0]
2565 if copy:
2565 if copy:
2566 copies.setdefault(rev, {})[fn] = copy
2566 copies.setdefault(rev, {})[fn] = copy
2567 if fn in skip:
2567 if fn in skip:
2568 if copy:
2568 if copy:
2569 skip[copy] = True
2569 skip[copy] = True
2570 continue
2570 continue
2571 files.append(fn)
2571 files.append(fn)
2572
2572
2573 if fn not in matches[rev]:
2573 if fn not in matches[rev]:
2574 grepbody(fn, rev, flog.read(fnode))
2574 grepbody(fn, rev, flog.read(fnode))
2575
2575
2576 pfn = copy or fn
2576 pfn = copy or fn
2577 if pfn not in matches[parent]:
2577 if pfn not in matches[parent]:
2578 try:
2578 try:
2579 fnode = pctx.filenode(pfn)
2579 fnode = pctx.filenode(pfn)
2580 grepbody(pfn, parent, flog.read(fnode))
2580 grepbody(pfn, parent, flog.read(fnode))
2581 except error.LookupError:
2581 except error.LookupError:
2582 pass
2582 pass
2583
2583
2584 ui.pager('grep')
2584 ui.pager('grep')
2585 fm = ui.formatter('grep', opts)
2585 fm = ui.formatter('grep', opts)
2586 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2586 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2587 rev = ctx.rev()
2587 rev = ctx.rev()
2588 parent = ctx.p1().rev()
2588 parent = ctx.p1().rev()
2589 for fn in sorted(revfiles.get(rev, [])):
2589 for fn in sorted(revfiles.get(rev, [])):
2590 states = matches[rev][fn]
2590 states = matches[rev][fn]
2591 copy = copies.get(rev, {}).get(fn)
2591 copy = copies.get(rev, {}).get(fn)
2592 if fn in skip:
2592 if fn in skip:
2593 if copy:
2593 if copy:
2594 skip[copy] = True
2594 skip[copy] = True
2595 continue
2595 continue
2596 pstates = matches.get(parent, {}).get(copy or fn, [])
2596 pstates = matches.get(parent, {}).get(copy or fn, [])
2597 if pstates or states:
2597 if pstates or states:
2598 r = display(fm, fn, ctx, pstates, states)
2598 r = display(fm, fn, ctx, pstates, states)
2599 found = found or r
2599 found = found or r
2600 if r and not opts.get('all'):
2600 if r and not opts.get('all'):
2601 skip[fn] = True
2601 skip[fn] = True
2602 if copy:
2602 if copy:
2603 skip[copy] = True
2603 skip[copy] = True
2604 del revfiles[rev]
2604 del revfiles[rev]
2605 # We will keep the matches dict for the duration of the window
2605 # We will keep the matches dict for the duration of the window
2606 # clear the matches dict once the window is over
2606 # clear the matches dict once the window is over
2607 if not revfiles:
2607 if not revfiles:
2608 matches.clear()
2608 matches.clear()
2609 fm.end()
2609 fm.end()
2610
2610
2611 return not found
2611 return not found
2612
2612
2613 @command('heads',
2613 @command('heads',
2614 [('r', 'rev', '',
2614 [('r', 'rev', '',
2615 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2615 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2616 ('t', 'topo', False, _('show topological heads only')),
2616 ('t', 'topo', False, _('show topological heads only')),
2617 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2617 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2618 ('c', 'closed', False, _('show normal and closed branch heads')),
2618 ('c', 'closed', False, _('show normal and closed branch heads')),
2619 ] + templateopts,
2619 ] + templateopts,
2620 _('[-ct] [-r STARTREV] [REV]...'), cmdtype=readonly)
2620 _('[-ct] [-r STARTREV] [REV]...'), cmdtype=readonly)
2621 def heads(ui, repo, *branchrevs, **opts):
2621 def heads(ui, repo, *branchrevs, **opts):
2622 """show branch heads
2622 """show branch heads
2623
2623
2624 With no arguments, show all open branch heads in the repository.
2624 With no arguments, show all open branch heads in the repository.
2625 Branch heads are changesets that have no descendants on the
2625 Branch heads are changesets that have no descendants on the
2626 same branch. They are where development generally takes place and
2626 same branch. They are where development generally takes place and
2627 are the usual targets for update and merge operations.
2627 are the usual targets for update and merge operations.
2628
2628
2629 If one or more REVs are given, only open branch heads on the
2629 If one or more REVs are given, only open branch heads on the
2630 branches associated with the specified changesets are shown. This
2630 branches associated with the specified changesets are shown. This
2631 means that you can use :hg:`heads .` to see the heads on the
2631 means that you can use :hg:`heads .` to see the heads on the
2632 currently checked-out branch.
2632 currently checked-out branch.
2633
2633
2634 If -c/--closed is specified, also show branch heads marked closed
2634 If -c/--closed is specified, also show branch heads marked closed
2635 (see :hg:`commit --close-branch`).
2635 (see :hg:`commit --close-branch`).
2636
2636
2637 If STARTREV is specified, only those heads that are descendants of
2637 If STARTREV is specified, only those heads that are descendants of
2638 STARTREV will be displayed.
2638 STARTREV will be displayed.
2639
2639
2640 If -t/--topo is specified, named branch mechanics will be ignored and only
2640 If -t/--topo is specified, named branch mechanics will be ignored and only
2641 topological heads (changesets with no children) will be shown.
2641 topological heads (changesets with no children) will be shown.
2642
2642
2643 Returns 0 if matching heads are found, 1 if not.
2643 Returns 0 if matching heads are found, 1 if not.
2644 """
2644 """
2645
2645
2646 opts = pycompat.byteskwargs(opts)
2646 opts = pycompat.byteskwargs(opts)
2647 start = None
2647 start = None
2648 rev = opts.get('rev')
2648 rev = opts.get('rev')
2649 if rev:
2649 if rev:
2650 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2650 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2651 start = scmutil.revsingle(repo, rev, None).node()
2651 start = scmutil.revsingle(repo, rev, None).node()
2652
2652
2653 if opts.get('topo'):
2653 if opts.get('topo'):
2654 heads = [repo[h] for h in repo.heads(start)]
2654 heads = [repo[h] for h in repo.heads(start)]
2655 else:
2655 else:
2656 heads = []
2656 heads = []
2657 for branch in repo.branchmap():
2657 for branch in repo.branchmap():
2658 heads += repo.branchheads(branch, start, opts.get('closed'))
2658 heads += repo.branchheads(branch, start, opts.get('closed'))
2659 heads = [repo[h] for h in heads]
2659 heads = [repo[h] for h in heads]
2660
2660
2661 if branchrevs:
2661 if branchrevs:
2662 branches = set(repo[r].branch()
2662 branches = set(repo[r].branch()
2663 for r in scmutil.revrange(repo, branchrevs))
2663 for r in scmutil.revrange(repo, branchrevs))
2664 heads = [h for h in heads if h.branch() in branches]
2664 heads = [h for h in heads if h.branch() in branches]
2665
2665
2666 if opts.get('active') and branchrevs:
2666 if opts.get('active') and branchrevs:
2667 dagheads = repo.heads(start)
2667 dagheads = repo.heads(start)
2668 heads = [h for h in heads if h.node() in dagheads]
2668 heads = [h for h in heads if h.node() in dagheads]
2669
2669
2670 if branchrevs:
2670 if branchrevs:
2671 haveheads = set(h.branch() for h in heads)
2671 haveheads = set(h.branch() for h in heads)
2672 if branches - haveheads:
2672 if branches - haveheads:
2673 headless = ', '.join(b for b in branches - haveheads)
2673 headless = ', '.join(b for b in branches - haveheads)
2674 msg = _('no open branch heads found on branches %s')
2674 msg = _('no open branch heads found on branches %s')
2675 if opts.get('rev'):
2675 if opts.get('rev'):
2676 msg += _(' (started at %s)') % opts['rev']
2676 msg += _(' (started at %s)') % opts['rev']
2677 ui.warn((msg + '\n') % headless)
2677 ui.warn((msg + '\n') % headless)
2678
2678
2679 if not heads:
2679 if not heads:
2680 return 1
2680 return 1
2681
2681
2682 ui.pager('heads')
2682 ui.pager('heads')
2683 heads = sorted(heads, key=lambda x: -x.rev())
2683 heads = sorted(heads, key=lambda x: -x.rev())
2684 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
2684 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
2685 for ctx in heads:
2685 for ctx in heads:
2686 displayer.show(ctx)
2686 displayer.show(ctx)
2687 displayer.close()
2687 displayer.close()
2688
2688
2689 @command('help',
2689 @command('help',
2690 [('e', 'extension', None, _('show only help for extensions')),
2690 [('e', 'extension', None, _('show only help for extensions')),
2691 ('c', 'command', None, _('show only help for commands')),
2691 ('c', 'command', None, _('show only help for commands')),
2692 ('k', 'keyword', None, _('show topics matching keyword')),
2692 ('k', 'keyword', None, _('show topics matching keyword')),
2693 ('s', 'system', [], _('show help for specific platform(s)')),
2693 ('s', 'system', [], _('show help for specific platform(s)')),
2694 ],
2694 ],
2695 _('[-ecks] [TOPIC]'),
2695 _('[-ecks] [TOPIC]'),
2696 norepo=True, cmdtype=readonly)
2696 norepo=True, cmdtype=readonly)
2697 def help_(ui, name=None, **opts):
2697 def help_(ui, name=None, **opts):
2698 """show help for a given topic or a help overview
2698 """show help for a given topic or a help overview
2699
2699
2700 With no arguments, print a list of commands with short help messages.
2700 With no arguments, print a list of commands with short help messages.
2701
2701
2702 Given a topic, extension, or command name, print help for that
2702 Given a topic, extension, or command name, print help for that
2703 topic.
2703 topic.
2704
2704
2705 Returns 0 if successful.
2705 Returns 0 if successful.
2706 """
2706 """
2707
2707
2708 keep = opts.get(r'system') or []
2708 keep = opts.get(r'system') or []
2709 if len(keep) == 0:
2709 if len(keep) == 0:
2710 if pycompat.sysplatform.startswith('win'):
2710 if pycompat.sysplatform.startswith('win'):
2711 keep.append('windows')
2711 keep.append('windows')
2712 elif pycompat.sysplatform == 'OpenVMS':
2712 elif pycompat.sysplatform == 'OpenVMS':
2713 keep.append('vms')
2713 keep.append('vms')
2714 elif pycompat.sysplatform == 'plan9':
2714 elif pycompat.sysplatform == 'plan9':
2715 keep.append('plan9')
2715 keep.append('plan9')
2716 else:
2716 else:
2717 keep.append('unix')
2717 keep.append('unix')
2718 keep.append(pycompat.sysplatform.lower())
2718 keep.append(pycompat.sysplatform.lower())
2719 if ui.verbose:
2719 if ui.verbose:
2720 keep.append('verbose')
2720 keep.append('verbose')
2721
2721
2722 commands = sys.modules[__name__]
2722 commands = sys.modules[__name__]
2723 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
2723 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
2724 ui.pager('help')
2724 ui.pager('help')
2725 ui.write(formatted)
2725 ui.write(formatted)
2726
2726
2727
2727
2728 @command('identify|id',
2728 @command('identify|id',
2729 [('r', 'rev', '',
2729 [('r', 'rev', '',
2730 _('identify the specified revision'), _('REV')),
2730 _('identify the specified revision'), _('REV')),
2731 ('n', 'num', None, _('show local revision number')),
2731 ('n', 'num', None, _('show local revision number')),
2732 ('i', 'id', None, _('show global revision id')),
2732 ('i', 'id', None, _('show global revision id')),
2733 ('b', 'branch', None, _('show branch')),
2733 ('b', 'branch', None, _('show branch')),
2734 ('t', 'tags', None, _('show tags')),
2734 ('t', 'tags', None, _('show tags')),
2735 ('B', 'bookmarks', None, _('show bookmarks')),
2735 ('B', 'bookmarks', None, _('show bookmarks')),
2736 ] + remoteopts + formatteropts,
2736 ] + remoteopts + formatteropts,
2737 _('[-nibtB] [-r REV] [SOURCE]'),
2737 _('[-nibtB] [-r REV] [SOURCE]'),
2738 optionalrepo=True, cmdtype=readonly)
2738 optionalrepo=True, cmdtype=readonly)
2739 def identify(ui, repo, source=None, rev=None,
2739 def identify(ui, repo, source=None, rev=None,
2740 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2740 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2741 """identify the working directory or specified revision
2741 """identify the working directory or specified revision
2742
2742
2743 Print a summary identifying the repository state at REV using one or
2743 Print a summary identifying the repository state at REV using one or
2744 two parent hash identifiers, followed by a "+" if the working
2744 two parent hash identifiers, followed by a "+" if the working
2745 directory has uncommitted changes, the branch name (if not default),
2745 directory has uncommitted changes, the branch name (if not default),
2746 a list of tags, and a list of bookmarks.
2746 a list of tags, and a list of bookmarks.
2747
2747
2748 When REV is not given, print a summary of the current state of the
2748 When REV is not given, print a summary of the current state of the
2749 repository including the working directory. Specify -r. to get information
2749 repository including the working directory. Specify -r. to get information
2750 of the working directory parent without scanning uncommitted changes.
2750 of the working directory parent without scanning uncommitted changes.
2751
2751
2752 Specifying a path to a repository root or Mercurial bundle will
2752 Specifying a path to a repository root or Mercurial bundle will
2753 cause lookup to operate on that repository/bundle.
2753 cause lookup to operate on that repository/bundle.
2754
2754
2755 .. container:: verbose
2755 .. container:: verbose
2756
2756
2757 Examples:
2757 Examples:
2758
2758
2759 - generate a build identifier for the working directory::
2759 - generate a build identifier for the working directory::
2760
2760
2761 hg id --id > build-id.dat
2761 hg id --id > build-id.dat
2762
2762
2763 - find the revision corresponding to a tag::
2763 - find the revision corresponding to a tag::
2764
2764
2765 hg id -n -r 1.3
2765 hg id -n -r 1.3
2766
2766
2767 - check the most recent revision of a remote repository::
2767 - check the most recent revision of a remote repository::
2768
2768
2769 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2769 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2770
2770
2771 See :hg:`log` for generating more information about specific revisions,
2771 See :hg:`log` for generating more information about specific revisions,
2772 including full hash identifiers.
2772 including full hash identifiers.
2773
2773
2774 Returns 0 if successful.
2774 Returns 0 if successful.
2775 """
2775 """
2776
2776
2777 opts = pycompat.byteskwargs(opts)
2777 opts = pycompat.byteskwargs(opts)
2778 if not repo and not source:
2778 if not repo and not source:
2779 raise error.Abort(_("there is no Mercurial repository here "
2779 raise error.Abort(_("there is no Mercurial repository here "
2780 "(.hg not found)"))
2780 "(.hg not found)"))
2781
2781
2782 if ui.debugflag:
2782 if ui.debugflag:
2783 hexfunc = hex
2783 hexfunc = hex
2784 else:
2784 else:
2785 hexfunc = short
2785 hexfunc = short
2786 default = not (num or id or branch or tags or bookmarks)
2786 default = not (num or id or branch or tags or bookmarks)
2787 output = []
2787 output = []
2788 revs = []
2788 revs = []
2789
2789
2790 if source:
2790 if source:
2791 source, branches = hg.parseurl(ui.expandpath(source))
2791 source, branches = hg.parseurl(ui.expandpath(source))
2792 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2792 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2793 repo = peer.local()
2793 repo = peer.local()
2794 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2794 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2795
2795
2796 fm = ui.formatter('identify', opts)
2796 fm = ui.formatter('identify', opts)
2797 fm.startitem()
2797 fm.startitem()
2798
2798
2799 if not repo:
2799 if not repo:
2800 if num or branch or tags:
2800 if num or branch or tags:
2801 raise error.Abort(
2801 raise error.Abort(
2802 _("can't query remote revision number, branch, or tags"))
2802 _("can't query remote revision number, branch, or tags"))
2803 if not rev and revs:
2803 if not rev and revs:
2804 rev = revs[0]
2804 rev = revs[0]
2805 if not rev:
2805 if not rev:
2806 rev = "tip"
2806 rev = "tip"
2807
2807
2808 remoterev = peer.lookup(rev)
2808 remoterev = peer.lookup(rev)
2809 hexrev = hexfunc(remoterev)
2809 hexrev = hexfunc(remoterev)
2810 if default or id:
2810 if default or id:
2811 output = [hexrev]
2811 output = [hexrev]
2812 fm.data(id=hexrev)
2812 fm.data(id=hexrev)
2813
2813
2814 def getbms():
2814 def getbms():
2815 bms = []
2815 bms = []
2816
2816
2817 if 'bookmarks' in peer.listkeys('namespaces'):
2817 if 'bookmarks' in peer.listkeys('namespaces'):
2818 hexremoterev = hex(remoterev)
2818 hexremoterev = hex(remoterev)
2819 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2819 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2820 if bmr == hexremoterev]
2820 if bmr == hexremoterev]
2821
2821
2822 return sorted(bms)
2822 return sorted(bms)
2823
2823
2824 bms = getbms()
2824 bms = getbms()
2825 if bookmarks:
2825 if bookmarks:
2826 output.extend(bms)
2826 output.extend(bms)
2827 elif default and not ui.quiet:
2827 elif default and not ui.quiet:
2828 # multiple bookmarks for a single parent separated by '/'
2828 # multiple bookmarks for a single parent separated by '/'
2829 bm = '/'.join(bms)
2829 bm = '/'.join(bms)
2830 if bm:
2830 if bm:
2831 output.append(bm)
2831 output.append(bm)
2832
2832
2833 fm.data(node=hex(remoterev))
2833 fm.data(node=hex(remoterev))
2834 fm.data(bookmarks=fm.formatlist(bms, name='bookmark'))
2834 fm.data(bookmarks=fm.formatlist(bms, name='bookmark'))
2835 else:
2835 else:
2836 if rev:
2836 if rev:
2837 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2837 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2838 ctx = scmutil.revsingle(repo, rev, None)
2838 ctx = scmutil.revsingle(repo, rev, None)
2839
2839
2840 if ctx.rev() is None:
2840 if ctx.rev() is None:
2841 ctx = repo[None]
2841 ctx = repo[None]
2842 parents = ctx.parents()
2842 parents = ctx.parents()
2843 taglist = []
2843 taglist = []
2844 for p in parents:
2844 for p in parents:
2845 taglist.extend(p.tags())
2845 taglist.extend(p.tags())
2846
2846
2847 dirty = ""
2847 dirty = ""
2848 if ctx.dirty(missing=True, merge=False, branch=False):
2848 if ctx.dirty(missing=True, merge=False, branch=False):
2849 dirty = '+'
2849 dirty = '+'
2850 fm.data(dirty=dirty)
2850 fm.data(dirty=dirty)
2851
2851
2852 hexoutput = [hexfunc(p.node()) for p in parents]
2852 hexoutput = [hexfunc(p.node()) for p in parents]
2853 if default or id:
2853 if default or id:
2854 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
2854 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
2855 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
2855 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
2856
2856
2857 if num:
2857 if num:
2858 numoutput = ["%d" % p.rev() for p in parents]
2858 numoutput = ["%d" % p.rev() for p in parents]
2859 output.append("%s%s" % ('+'.join(numoutput), dirty))
2859 output.append("%s%s" % ('+'.join(numoutput), dirty))
2860
2860
2861 fn = fm.nested('parents', tmpl='{rev}:{node|formatnode}', sep=' ')
2861 fn = fm.nested('parents', tmpl='{rev}:{node|formatnode}', sep=' ')
2862 for p in parents:
2862 for p in parents:
2863 fn.startitem()
2863 fn.startitem()
2864 fn.data(rev=p.rev())
2864 fn.data(rev=p.rev())
2865 fn.data(node=p.hex())
2865 fn.data(node=p.hex())
2866 fn.context(ctx=p)
2866 fn.context(ctx=p)
2867 fn.end()
2867 fn.end()
2868 else:
2868 else:
2869 hexoutput = hexfunc(ctx.node())
2869 hexoutput = hexfunc(ctx.node())
2870 if default or id:
2870 if default or id:
2871 output = [hexoutput]
2871 output = [hexoutput]
2872 fm.data(id=hexoutput)
2872 fm.data(id=hexoutput)
2873
2873
2874 if num:
2874 if num:
2875 output.append(pycompat.bytestr(ctx.rev()))
2875 output.append(pycompat.bytestr(ctx.rev()))
2876 taglist = ctx.tags()
2876 taglist = ctx.tags()
2877
2877
2878 if default and not ui.quiet:
2878 if default and not ui.quiet:
2879 b = ctx.branch()
2879 b = ctx.branch()
2880 if b != 'default':
2880 if b != 'default':
2881 output.append("(%s)" % b)
2881 output.append("(%s)" % b)
2882
2882
2883 # multiple tags for a single parent separated by '/'
2883 # multiple tags for a single parent separated by '/'
2884 t = '/'.join(taglist)
2884 t = '/'.join(taglist)
2885 if t:
2885 if t:
2886 output.append(t)
2886 output.append(t)
2887
2887
2888 # multiple bookmarks for a single parent separated by '/'
2888 # multiple bookmarks for a single parent separated by '/'
2889 bm = '/'.join(ctx.bookmarks())
2889 bm = '/'.join(ctx.bookmarks())
2890 if bm:
2890 if bm:
2891 output.append(bm)
2891 output.append(bm)
2892 else:
2892 else:
2893 if branch:
2893 if branch:
2894 output.append(ctx.branch())
2894 output.append(ctx.branch())
2895
2895
2896 if tags:
2896 if tags:
2897 output.extend(taglist)
2897 output.extend(taglist)
2898
2898
2899 if bookmarks:
2899 if bookmarks:
2900 output.extend(ctx.bookmarks())
2900 output.extend(ctx.bookmarks())
2901
2901
2902 fm.data(node=ctx.hex())
2902 fm.data(node=ctx.hex())
2903 fm.data(branch=ctx.branch())
2903 fm.data(branch=ctx.branch())
2904 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
2904 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
2905 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
2905 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
2906 fm.context(ctx=ctx)
2906 fm.context(ctx=ctx)
2907
2907
2908 fm.plain("%s\n" % ' '.join(output))
2908 fm.plain("%s\n" % ' '.join(output))
2909 fm.end()
2909 fm.end()
2910
2910
2911 @command('import|patch',
2911 @command('import|patch',
2912 [('p', 'strip', 1,
2912 [('p', 'strip', 1,
2913 _('directory strip option for patch. This has the same '
2913 _('directory strip option for patch. This has the same '
2914 'meaning as the corresponding patch option'), _('NUM')),
2914 'meaning as the corresponding patch option'), _('NUM')),
2915 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
2915 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
2916 ('e', 'edit', False, _('invoke editor on commit messages')),
2916 ('e', 'edit', False, _('invoke editor on commit messages')),
2917 ('f', 'force', None,
2917 ('f', 'force', None,
2918 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
2918 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
2919 ('', 'no-commit', None,
2919 ('', 'no-commit', None,
2920 _("don't commit, just update the working directory")),
2920 _("don't commit, just update the working directory")),
2921 ('', 'bypass', None,
2921 ('', 'bypass', None,
2922 _("apply patch without touching the working directory")),
2922 _("apply patch without touching the working directory")),
2923 ('', 'partial', None,
2923 ('', 'partial', None,
2924 _('commit even if some hunks fail')),
2924 _('commit even if some hunks fail')),
2925 ('', 'exact', None,
2925 ('', 'exact', None,
2926 _('abort if patch would apply lossily')),
2926 _('abort if patch would apply lossily')),
2927 ('', 'prefix', '',
2927 ('', 'prefix', '',
2928 _('apply patch to subdirectory'), _('DIR')),
2928 _('apply patch to subdirectory'), _('DIR')),
2929 ('', 'import-branch', None,
2929 ('', 'import-branch', None,
2930 _('use any branch information in patch (implied by --exact)'))] +
2930 _('use any branch information in patch (implied by --exact)'))] +
2931 commitopts + commitopts2 + similarityopts,
2931 commitopts + commitopts2 + similarityopts,
2932 _('[OPTION]... PATCH...'))
2932 _('[OPTION]... PATCH...'))
2933 def import_(ui, repo, patch1=None, *patches, **opts):
2933 def import_(ui, repo, patch1=None, *patches, **opts):
2934 """import an ordered set of patches
2934 """import an ordered set of patches
2935
2935
2936 Import a list of patches and commit them individually (unless
2936 Import a list of patches and commit them individually (unless
2937 --no-commit is specified).
2937 --no-commit is specified).
2938
2938
2939 To read a patch from standard input (stdin), use "-" as the patch
2939 To read a patch from standard input (stdin), use "-" as the patch
2940 name. If a URL is specified, the patch will be downloaded from
2940 name. If a URL is specified, the patch will be downloaded from
2941 there.
2941 there.
2942
2942
2943 Import first applies changes to the working directory (unless
2943 Import first applies changes to the working directory (unless
2944 --bypass is specified), import will abort if there are outstanding
2944 --bypass is specified), import will abort if there are outstanding
2945 changes.
2945 changes.
2946
2946
2947 Use --bypass to apply and commit patches directly to the
2947 Use --bypass to apply and commit patches directly to the
2948 repository, without affecting the working directory. Without
2948 repository, without affecting the working directory. Without
2949 --exact, patches will be applied on top of the working directory
2949 --exact, patches will be applied on top of the working directory
2950 parent revision.
2950 parent revision.
2951
2951
2952 You can import a patch straight from a mail message. Even patches
2952 You can import a patch straight from a mail message. Even patches
2953 as attachments work (to use the body part, it must have type
2953 as attachments work (to use the body part, it must have type
2954 text/plain or text/x-patch). From and Subject headers of email
2954 text/plain or text/x-patch). From and Subject headers of email
2955 message are used as default committer and commit message. All
2955 message are used as default committer and commit message. All
2956 text/plain body parts before first diff are added to the commit
2956 text/plain body parts before first diff are added to the commit
2957 message.
2957 message.
2958
2958
2959 If the imported patch was generated by :hg:`export`, user and
2959 If the imported patch was generated by :hg:`export`, user and
2960 description from patch override values from message headers and
2960 description from patch override values from message headers and
2961 body. Values given on command line with -m/--message and -u/--user
2961 body. Values given on command line with -m/--message and -u/--user
2962 override these.
2962 override these.
2963
2963
2964 If --exact is specified, import will set the working directory to
2964 If --exact is specified, import will set the working directory to
2965 the parent of each patch before applying it, and will abort if the
2965 the parent of each patch before applying it, and will abort if the
2966 resulting changeset has a different ID than the one recorded in
2966 resulting changeset has a different ID than the one recorded in
2967 the patch. This will guard against various ways that portable
2967 the patch. This will guard against various ways that portable
2968 patch formats and mail systems might fail to transfer Mercurial
2968 patch formats and mail systems might fail to transfer Mercurial
2969 data or metadata. See :hg:`bundle` for lossless transmission.
2969 data or metadata. See :hg:`bundle` for lossless transmission.
2970
2970
2971 Use --partial to ensure a changeset will be created from the patch
2971 Use --partial to ensure a changeset will be created from the patch
2972 even if some hunks fail to apply. Hunks that fail to apply will be
2972 even if some hunks fail to apply. Hunks that fail to apply will be
2973 written to a <target-file>.rej file. Conflicts can then be resolved
2973 written to a <target-file>.rej file. Conflicts can then be resolved
2974 by hand before :hg:`commit --amend` is run to update the created
2974 by hand before :hg:`commit --amend` is run to update the created
2975 changeset. This flag exists to let people import patches that
2975 changeset. This flag exists to let people import patches that
2976 partially apply without losing the associated metadata (author,
2976 partially apply without losing the associated metadata (author,
2977 date, description, ...).
2977 date, description, ...).
2978
2978
2979 .. note::
2979 .. note::
2980
2980
2981 When no hunks apply cleanly, :hg:`import --partial` will create
2981 When no hunks apply cleanly, :hg:`import --partial` will create
2982 an empty changeset, importing only the patch metadata.
2982 an empty changeset, importing only the patch metadata.
2983
2983
2984 With -s/--similarity, hg will attempt to discover renames and
2984 With -s/--similarity, hg will attempt to discover renames and
2985 copies in the patch in the same way as :hg:`addremove`.
2985 copies in the patch in the same way as :hg:`addremove`.
2986
2986
2987 It is possible to use external patch programs to perform the patch
2987 It is possible to use external patch programs to perform the patch
2988 by setting the ``ui.patch`` configuration option. For the default
2988 by setting the ``ui.patch`` configuration option. For the default
2989 internal tool, the fuzz can also be configured via ``patch.fuzz``.
2989 internal tool, the fuzz can also be configured via ``patch.fuzz``.
2990 See :hg:`help config` for more information about configuration
2990 See :hg:`help config` for more information about configuration
2991 files and how to use these options.
2991 files and how to use these options.
2992
2992
2993 See :hg:`help dates` for a list of formats valid for -d/--date.
2993 See :hg:`help dates` for a list of formats valid for -d/--date.
2994
2994
2995 .. container:: verbose
2995 .. container:: verbose
2996
2996
2997 Examples:
2997 Examples:
2998
2998
2999 - import a traditional patch from a website and detect renames::
2999 - import a traditional patch from a website and detect renames::
3000
3000
3001 hg import -s 80 http://example.com/bugfix.patch
3001 hg import -s 80 http://example.com/bugfix.patch
3002
3002
3003 - import a changeset from an hgweb server::
3003 - import a changeset from an hgweb server::
3004
3004
3005 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3005 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3006
3006
3007 - import all the patches in an Unix-style mbox::
3007 - import all the patches in an Unix-style mbox::
3008
3008
3009 hg import incoming-patches.mbox
3009 hg import incoming-patches.mbox
3010
3010
3011 - import patches from stdin::
3011 - import patches from stdin::
3012
3012
3013 hg import -
3013 hg import -
3014
3014
3015 - attempt to exactly restore an exported changeset (not always
3015 - attempt to exactly restore an exported changeset (not always
3016 possible)::
3016 possible)::
3017
3017
3018 hg import --exact proposed-fix.patch
3018 hg import --exact proposed-fix.patch
3019
3019
3020 - use an external tool to apply a patch which is too fuzzy for
3020 - use an external tool to apply a patch which is too fuzzy for
3021 the default internal tool.
3021 the default internal tool.
3022
3022
3023 hg import --config ui.patch="patch --merge" fuzzy.patch
3023 hg import --config ui.patch="patch --merge" fuzzy.patch
3024
3024
3025 - change the default fuzzing from 2 to a less strict 7
3025 - change the default fuzzing from 2 to a less strict 7
3026
3026
3027 hg import --config ui.fuzz=7 fuzz.patch
3027 hg import --config ui.fuzz=7 fuzz.patch
3028
3028
3029 Returns 0 on success, 1 on partial success (see --partial).
3029 Returns 0 on success, 1 on partial success (see --partial).
3030 """
3030 """
3031
3031
3032 opts = pycompat.byteskwargs(opts)
3032 opts = pycompat.byteskwargs(opts)
3033 if not patch1:
3033 if not patch1:
3034 raise error.Abort(_('need at least one patch to import'))
3034 raise error.Abort(_('need at least one patch to import'))
3035
3035
3036 patches = (patch1,) + patches
3036 patches = (patch1,) + patches
3037
3037
3038 date = opts.get('date')
3038 date = opts.get('date')
3039 if date:
3039 if date:
3040 opts['date'] = dateutil.parsedate(date)
3040 opts['date'] = dateutil.parsedate(date)
3041
3041
3042 exact = opts.get('exact')
3042 exact = opts.get('exact')
3043 update = not opts.get('bypass')
3043 update = not opts.get('bypass')
3044 if not update and opts.get('no_commit'):
3044 if not update and opts.get('no_commit'):
3045 raise error.Abort(_('cannot use --no-commit with --bypass'))
3045 raise error.Abort(_('cannot use --no-commit with --bypass'))
3046 try:
3046 try:
3047 sim = float(opts.get('similarity') or 0)
3047 sim = float(opts.get('similarity') or 0)
3048 except ValueError:
3048 except ValueError:
3049 raise error.Abort(_('similarity must be a number'))
3049 raise error.Abort(_('similarity must be a number'))
3050 if sim < 0 or sim > 100:
3050 if sim < 0 or sim > 100:
3051 raise error.Abort(_('similarity must be between 0 and 100'))
3051 raise error.Abort(_('similarity must be between 0 and 100'))
3052 if sim and not update:
3052 if sim and not update:
3053 raise error.Abort(_('cannot use --similarity with --bypass'))
3053 raise error.Abort(_('cannot use --similarity with --bypass'))
3054 if exact:
3054 if exact:
3055 if opts.get('edit'):
3055 if opts.get('edit'):
3056 raise error.Abort(_('cannot use --exact with --edit'))
3056 raise error.Abort(_('cannot use --exact with --edit'))
3057 if opts.get('prefix'):
3057 if opts.get('prefix'):
3058 raise error.Abort(_('cannot use --exact with --prefix'))
3058 raise error.Abort(_('cannot use --exact with --prefix'))
3059
3059
3060 base = opts["base"]
3060 base = opts["base"]
3061 wlock = dsguard = lock = tr = None
3061 wlock = dsguard = lock = tr = None
3062 msgs = []
3062 msgs = []
3063 ret = 0
3063 ret = 0
3064
3064
3065
3065
3066 try:
3066 try:
3067 wlock = repo.wlock()
3067 wlock = repo.wlock()
3068
3068
3069 if update:
3069 if update:
3070 cmdutil.checkunfinished(repo)
3070 cmdutil.checkunfinished(repo)
3071 if (exact or not opts.get('force')):
3071 if (exact or not opts.get('force')):
3072 cmdutil.bailifchanged(repo)
3072 cmdutil.bailifchanged(repo)
3073
3073
3074 if not opts.get('no_commit'):
3074 if not opts.get('no_commit'):
3075 lock = repo.lock()
3075 lock = repo.lock()
3076 tr = repo.transaction('import')
3076 tr = repo.transaction('import')
3077 else:
3077 else:
3078 dsguard = dirstateguard.dirstateguard(repo, 'import')
3078 dsguard = dirstateguard.dirstateguard(repo, 'import')
3079 parents = repo[None].parents()
3079 parents = repo[None].parents()
3080 for patchurl in patches:
3080 for patchurl in patches:
3081 if patchurl == '-':
3081 if patchurl == '-':
3082 ui.status(_('applying patch from stdin\n'))
3082 ui.status(_('applying patch from stdin\n'))
3083 patchfile = ui.fin
3083 patchfile = ui.fin
3084 patchurl = 'stdin' # for error message
3084 patchurl = 'stdin' # for error message
3085 else:
3085 else:
3086 patchurl = os.path.join(base, patchurl)
3086 patchurl = os.path.join(base, patchurl)
3087 ui.status(_('applying %s\n') % patchurl)
3087 ui.status(_('applying %s\n') % patchurl)
3088 patchfile = hg.openpath(ui, patchurl)
3088 patchfile = hg.openpath(ui, patchurl)
3089
3089
3090 haspatch = False
3090 haspatch = False
3091 for hunk in patch.split(patchfile):
3091 for hunk in patch.split(patchfile):
3092 patchdata = patch.extract(ui, hunk)
3092 with patch.extract(ui, hunk) as patchdata:
3093
3093 msg, node, rej = cmdutil.tryimportone(ui, repo, patchdata,
3094 msg, node, rej = cmdutil.tryimportone(ui, repo, patchdata,
3094 parents, opts,
3095 parents, opts,
3095 msgs, hg.clean)
3096 msgs, hg.clean)
3097 if msg:
3096 if msg:
3098 haspatch = True
3097 haspatch = True
3099 ui.note(msg + '\n')
3098 ui.note(msg + '\n')
3100 if update or exact:
3099 if update or exact:
3101 parents = repo[None].parents()
3100 parents = repo[None].parents()
3102 else:
3101 else:
3103 parents = [repo[node]]
3102 parents = [repo[node]]
3104 if rej:
3103 if rej:
3105 ui.write_err(_("patch applied partially\n"))
3104 ui.write_err(_("patch applied partially\n"))
3106 ui.write_err(_("(fix the .rej files and run "
3105 ui.write_err(_("(fix the .rej files and run "
3107 "`hg commit --amend`)\n"))
3106 "`hg commit --amend`)\n"))
3108 ret = 1
3107 ret = 1
3109 break
3108 break
3110
3109
3111 if not haspatch:
3110 if not haspatch:
3112 raise error.Abort(_('%s: no diffs found') % patchurl)
3111 raise error.Abort(_('%s: no diffs found') % patchurl)
3113
3112
3114 if tr:
3113 if tr:
3115 tr.close()
3114 tr.close()
3116 if msgs:
3115 if msgs:
3117 repo.savecommitmessage('\n* * *\n'.join(msgs))
3116 repo.savecommitmessage('\n* * *\n'.join(msgs))
3118 if dsguard:
3117 if dsguard:
3119 dsguard.close()
3118 dsguard.close()
3120 return ret
3119 return ret
3121 finally:
3120 finally:
3122 if tr:
3121 if tr:
3123 tr.release()
3122 tr.release()
3124 release(lock, dsguard, wlock)
3123 release(lock, dsguard, wlock)
3125
3124
3126 @command('incoming|in',
3125 @command('incoming|in',
3127 [('f', 'force', None,
3126 [('f', 'force', None,
3128 _('run even if remote repository is unrelated')),
3127 _('run even if remote repository is unrelated')),
3129 ('n', 'newest-first', None, _('show newest record first')),
3128 ('n', 'newest-first', None, _('show newest record first')),
3130 ('', 'bundle', '',
3129 ('', 'bundle', '',
3131 _('file to store the bundles into'), _('FILE')),
3130 _('file to store the bundles into'), _('FILE')),
3132 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3131 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3133 ('B', 'bookmarks', False, _("compare bookmarks")),
3132 ('B', 'bookmarks', False, _("compare bookmarks")),
3134 ('b', 'branch', [],
3133 ('b', 'branch', [],
3135 _('a specific branch you would like to pull'), _('BRANCH')),
3134 _('a specific branch you would like to pull'), _('BRANCH')),
3136 ] + logopts + remoteopts + subrepoopts,
3135 ] + logopts + remoteopts + subrepoopts,
3137 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3136 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3138 def incoming(ui, repo, source="default", **opts):
3137 def incoming(ui, repo, source="default", **opts):
3139 """show new changesets found in source
3138 """show new changesets found in source
3140
3139
3141 Show new changesets found in the specified path/URL or the default
3140 Show new changesets found in the specified path/URL or the default
3142 pull location. These are the changesets that would have been pulled
3141 pull location. These are the changesets that would have been pulled
3143 by :hg:`pull` at the time you issued this command.
3142 by :hg:`pull` at the time you issued this command.
3144
3143
3145 See pull for valid source format details.
3144 See pull for valid source format details.
3146
3145
3147 .. container:: verbose
3146 .. container:: verbose
3148
3147
3149 With -B/--bookmarks, the result of bookmark comparison between
3148 With -B/--bookmarks, the result of bookmark comparison between
3150 local and remote repositories is displayed. With -v/--verbose,
3149 local and remote repositories is displayed. With -v/--verbose,
3151 status is also displayed for each bookmark like below::
3150 status is also displayed for each bookmark like below::
3152
3151
3153 BM1 01234567890a added
3152 BM1 01234567890a added
3154 BM2 1234567890ab advanced
3153 BM2 1234567890ab advanced
3155 BM3 234567890abc diverged
3154 BM3 234567890abc diverged
3156 BM4 34567890abcd changed
3155 BM4 34567890abcd changed
3157
3156
3158 The action taken locally when pulling depends on the
3157 The action taken locally when pulling depends on the
3159 status of each bookmark:
3158 status of each bookmark:
3160
3159
3161 :``added``: pull will create it
3160 :``added``: pull will create it
3162 :``advanced``: pull will update it
3161 :``advanced``: pull will update it
3163 :``diverged``: pull will create a divergent bookmark
3162 :``diverged``: pull will create a divergent bookmark
3164 :``changed``: result depends on remote changesets
3163 :``changed``: result depends on remote changesets
3165
3164
3166 From the point of view of pulling behavior, bookmark
3165 From the point of view of pulling behavior, bookmark
3167 existing only in the remote repository are treated as ``added``,
3166 existing only in the remote repository are treated as ``added``,
3168 even if it is in fact locally deleted.
3167 even if it is in fact locally deleted.
3169
3168
3170 .. container:: verbose
3169 .. container:: verbose
3171
3170
3172 For remote repository, using --bundle avoids downloading the
3171 For remote repository, using --bundle avoids downloading the
3173 changesets twice if the incoming is followed by a pull.
3172 changesets twice if the incoming is followed by a pull.
3174
3173
3175 Examples:
3174 Examples:
3176
3175
3177 - show incoming changes with patches and full description::
3176 - show incoming changes with patches and full description::
3178
3177
3179 hg incoming -vp
3178 hg incoming -vp
3180
3179
3181 - show incoming changes excluding merges, store a bundle::
3180 - show incoming changes excluding merges, store a bundle::
3182
3181
3183 hg in -vpM --bundle incoming.hg
3182 hg in -vpM --bundle incoming.hg
3184 hg pull incoming.hg
3183 hg pull incoming.hg
3185
3184
3186 - briefly list changes inside a bundle::
3185 - briefly list changes inside a bundle::
3187
3186
3188 hg in changes.hg -T "{desc|firstline}\\n"
3187 hg in changes.hg -T "{desc|firstline}\\n"
3189
3188
3190 Returns 0 if there are incoming changes, 1 otherwise.
3189 Returns 0 if there are incoming changes, 1 otherwise.
3191 """
3190 """
3192 opts = pycompat.byteskwargs(opts)
3191 opts = pycompat.byteskwargs(opts)
3193 if opts.get('graph'):
3192 if opts.get('graph'):
3194 logcmdutil.checkunsupportedgraphflags([], opts)
3193 logcmdutil.checkunsupportedgraphflags([], opts)
3195 def display(other, chlist, displayer):
3194 def display(other, chlist, displayer):
3196 revdag = logcmdutil.graphrevs(other, chlist, opts)
3195 revdag = logcmdutil.graphrevs(other, chlist, opts)
3197 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3196 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3198 graphmod.asciiedges)
3197 graphmod.asciiedges)
3199
3198
3200 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3199 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3201 return 0
3200 return 0
3202
3201
3203 if opts.get('bundle') and opts.get('subrepos'):
3202 if opts.get('bundle') and opts.get('subrepos'):
3204 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3203 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3205
3204
3206 if opts.get('bookmarks'):
3205 if opts.get('bookmarks'):
3207 source, branches = hg.parseurl(ui.expandpath(source),
3206 source, branches = hg.parseurl(ui.expandpath(source),
3208 opts.get('branch'))
3207 opts.get('branch'))
3209 other = hg.peer(repo, opts, source)
3208 other = hg.peer(repo, opts, source)
3210 if 'bookmarks' not in other.listkeys('namespaces'):
3209 if 'bookmarks' not in other.listkeys('namespaces'):
3211 ui.warn(_("remote doesn't support bookmarks\n"))
3210 ui.warn(_("remote doesn't support bookmarks\n"))
3212 return 0
3211 return 0
3213 ui.pager('incoming')
3212 ui.pager('incoming')
3214 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3213 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3215 return bookmarks.incoming(ui, repo, other)
3214 return bookmarks.incoming(ui, repo, other)
3216
3215
3217 repo._subtoppath = ui.expandpath(source)
3216 repo._subtoppath = ui.expandpath(source)
3218 try:
3217 try:
3219 return hg.incoming(ui, repo, source, opts)
3218 return hg.incoming(ui, repo, source, opts)
3220 finally:
3219 finally:
3221 del repo._subtoppath
3220 del repo._subtoppath
3222
3221
3223
3222
3224 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3223 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3225 norepo=True)
3224 norepo=True)
3226 def init(ui, dest=".", **opts):
3225 def init(ui, dest=".", **opts):
3227 """create a new repository in the given directory
3226 """create a new repository in the given directory
3228
3227
3229 Initialize a new repository in the given directory. If the given
3228 Initialize a new repository in the given directory. If the given
3230 directory does not exist, it will be created.
3229 directory does not exist, it will be created.
3231
3230
3232 If no directory is given, the current directory is used.
3231 If no directory is given, the current directory is used.
3233
3232
3234 It is possible to specify an ``ssh://`` URL as the destination.
3233 It is possible to specify an ``ssh://`` URL as the destination.
3235 See :hg:`help urls` for more information.
3234 See :hg:`help urls` for more information.
3236
3235
3237 Returns 0 on success.
3236 Returns 0 on success.
3238 """
3237 """
3239 opts = pycompat.byteskwargs(opts)
3238 opts = pycompat.byteskwargs(opts)
3240 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3239 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3241
3240
3242 @command('locate',
3241 @command('locate',
3243 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3242 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3244 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3243 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3245 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3244 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3246 ] + walkopts,
3245 ] + walkopts,
3247 _('[OPTION]... [PATTERN]...'))
3246 _('[OPTION]... [PATTERN]...'))
3248 def locate(ui, repo, *pats, **opts):
3247 def locate(ui, repo, *pats, **opts):
3249 """locate files matching specific patterns (DEPRECATED)
3248 """locate files matching specific patterns (DEPRECATED)
3250
3249
3251 Print files under Mercurial control in the working directory whose
3250 Print files under Mercurial control in the working directory whose
3252 names match the given patterns.
3251 names match the given patterns.
3253
3252
3254 By default, this command searches all directories in the working
3253 By default, this command searches all directories in the working
3255 directory. To search just the current directory and its
3254 directory. To search just the current directory and its
3256 subdirectories, use "--include .".
3255 subdirectories, use "--include .".
3257
3256
3258 If no patterns are given to match, this command prints the names
3257 If no patterns are given to match, this command prints the names
3259 of all files under Mercurial control in the working directory.
3258 of all files under Mercurial control in the working directory.
3260
3259
3261 If you want to feed the output of this command into the "xargs"
3260 If you want to feed the output of this command into the "xargs"
3262 command, use the -0 option to both this command and "xargs". This
3261 command, use the -0 option to both this command and "xargs". This
3263 will avoid the problem of "xargs" treating single filenames that
3262 will avoid the problem of "xargs" treating single filenames that
3264 contain whitespace as multiple filenames.
3263 contain whitespace as multiple filenames.
3265
3264
3266 See :hg:`help files` for a more versatile command.
3265 See :hg:`help files` for a more versatile command.
3267
3266
3268 Returns 0 if a match is found, 1 otherwise.
3267 Returns 0 if a match is found, 1 otherwise.
3269 """
3268 """
3270 opts = pycompat.byteskwargs(opts)
3269 opts = pycompat.byteskwargs(opts)
3271 if opts.get('print0'):
3270 if opts.get('print0'):
3272 end = '\0'
3271 end = '\0'
3273 else:
3272 else:
3274 end = '\n'
3273 end = '\n'
3275 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3274 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3276
3275
3277 ret = 1
3276 ret = 1
3278 m = scmutil.match(ctx, pats, opts, default='relglob',
3277 m = scmutil.match(ctx, pats, opts, default='relglob',
3279 badfn=lambda x, y: False)
3278 badfn=lambda x, y: False)
3280
3279
3281 ui.pager('locate')
3280 ui.pager('locate')
3282 for abs in ctx.matches(m):
3281 for abs in ctx.matches(m):
3283 if opts.get('fullpath'):
3282 if opts.get('fullpath'):
3284 ui.write(repo.wjoin(abs), end)
3283 ui.write(repo.wjoin(abs), end)
3285 else:
3284 else:
3286 ui.write(((pats and m.rel(abs)) or abs), end)
3285 ui.write(((pats and m.rel(abs)) or abs), end)
3287 ret = 0
3286 ret = 0
3288
3287
3289 return ret
3288 return ret
3290
3289
3291 @command('^log|history',
3290 @command('^log|history',
3292 [('f', 'follow', None,
3291 [('f', 'follow', None,
3293 _('follow changeset history, or file history across copies and renames')),
3292 _('follow changeset history, or file history across copies and renames')),
3294 ('', 'follow-first', None,
3293 ('', 'follow-first', None,
3295 _('only follow the first parent of merge changesets (DEPRECATED)')),
3294 _('only follow the first parent of merge changesets (DEPRECATED)')),
3296 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3295 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3297 ('C', 'copies', None, _('show copied files')),
3296 ('C', 'copies', None, _('show copied files')),
3298 ('k', 'keyword', [],
3297 ('k', 'keyword', [],
3299 _('do case-insensitive search for a given text'), _('TEXT')),
3298 _('do case-insensitive search for a given text'), _('TEXT')),
3300 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3299 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3301 ('L', 'line-range', [],
3300 ('L', 'line-range', [],
3302 _('follow line range of specified file (EXPERIMENTAL)'),
3301 _('follow line range of specified file (EXPERIMENTAL)'),
3303 _('FILE,RANGE')),
3302 _('FILE,RANGE')),
3304 ('', 'removed', None, _('include revisions where files were removed')),
3303 ('', 'removed', None, _('include revisions where files were removed')),
3305 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3304 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3306 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3305 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3307 ('', 'only-branch', [],
3306 ('', 'only-branch', [],
3308 _('show only changesets within the given named branch (DEPRECATED)'),
3307 _('show only changesets within the given named branch (DEPRECATED)'),
3309 _('BRANCH')),
3308 _('BRANCH')),
3310 ('b', 'branch', [],
3309 ('b', 'branch', [],
3311 _('show changesets within the given named branch'), _('BRANCH')),
3310 _('show changesets within the given named branch'), _('BRANCH')),
3312 ('P', 'prune', [],
3311 ('P', 'prune', [],
3313 _('do not display revision or any of its ancestors'), _('REV')),
3312 _('do not display revision or any of its ancestors'), _('REV')),
3314 ] + logopts + walkopts,
3313 ] + logopts + walkopts,
3315 _('[OPTION]... [FILE]'),
3314 _('[OPTION]... [FILE]'),
3316 inferrepo=True, cmdtype=readonly)
3315 inferrepo=True, cmdtype=readonly)
3317 def log(ui, repo, *pats, **opts):
3316 def log(ui, repo, *pats, **opts):
3318 """show revision history of entire repository or files
3317 """show revision history of entire repository or files
3319
3318
3320 Print the revision history of the specified files or the entire
3319 Print the revision history of the specified files or the entire
3321 project.
3320 project.
3322
3321
3323 If no revision range is specified, the default is ``tip:0`` unless
3322 If no revision range is specified, the default is ``tip:0`` unless
3324 --follow is set, in which case the working directory parent is
3323 --follow is set, in which case the working directory parent is
3325 used as the starting revision.
3324 used as the starting revision.
3326
3325
3327 File history is shown without following rename or copy history of
3326 File history is shown without following rename or copy history of
3328 files. Use -f/--follow with a filename to follow history across
3327 files. Use -f/--follow with a filename to follow history across
3329 renames and copies. --follow without a filename will only show
3328 renames and copies. --follow without a filename will only show
3330 ancestors of the starting revision.
3329 ancestors of the starting revision.
3331
3330
3332 By default this command prints revision number and changeset id,
3331 By default this command prints revision number and changeset id,
3333 tags, non-trivial parents, user, date and time, and a summary for
3332 tags, non-trivial parents, user, date and time, and a summary for
3334 each commit. When the -v/--verbose switch is used, the list of
3333 each commit. When the -v/--verbose switch is used, the list of
3335 changed files and full commit message are shown.
3334 changed files and full commit message are shown.
3336
3335
3337 With --graph the revisions are shown as an ASCII art DAG with the most
3336 With --graph the revisions are shown as an ASCII art DAG with the most
3338 recent changeset at the top.
3337 recent changeset at the top.
3339 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3338 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3340 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3339 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3341 changeset from the lines below is a parent of the 'o' merge on the same
3340 changeset from the lines below is a parent of the 'o' merge on the same
3342 line.
3341 line.
3343 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3342 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3344 of a '|' indicates one or more revisions in a path are omitted.
3343 of a '|' indicates one or more revisions in a path are omitted.
3345
3344
3346 .. container:: verbose
3345 .. container:: verbose
3347
3346
3348 Use -L/--line-range FILE,M:N options to follow the history of lines
3347 Use -L/--line-range FILE,M:N options to follow the history of lines
3349 from M to N in FILE. With -p/--patch only diff hunks affecting
3348 from M to N in FILE. With -p/--patch only diff hunks affecting
3350 specified line range will be shown. This option requires --follow;
3349 specified line range will be shown. This option requires --follow;
3351 it can be specified multiple times. Currently, this option is not
3350 it can be specified multiple times. Currently, this option is not
3352 compatible with --graph. This option is experimental.
3351 compatible with --graph. This option is experimental.
3353
3352
3354 .. note::
3353 .. note::
3355
3354
3356 :hg:`log --patch` may generate unexpected diff output for merge
3355 :hg:`log --patch` may generate unexpected diff output for merge
3357 changesets, as it will only compare the merge changeset against
3356 changesets, as it will only compare the merge changeset against
3358 its first parent. Also, only files different from BOTH parents
3357 its first parent. Also, only files different from BOTH parents
3359 will appear in files:.
3358 will appear in files:.
3360
3359
3361 .. note::
3360 .. note::
3362
3361
3363 For performance reasons, :hg:`log FILE` may omit duplicate changes
3362 For performance reasons, :hg:`log FILE` may omit duplicate changes
3364 made on branches and will not show removals or mode changes. To
3363 made on branches and will not show removals or mode changes. To
3365 see all such changes, use the --removed switch.
3364 see all such changes, use the --removed switch.
3366
3365
3367 .. container:: verbose
3366 .. container:: verbose
3368
3367
3369 .. note::
3368 .. note::
3370
3369
3371 The history resulting from -L/--line-range options depends on diff
3370 The history resulting from -L/--line-range options depends on diff
3372 options; for instance if white-spaces are ignored, respective changes
3371 options; for instance if white-spaces are ignored, respective changes
3373 with only white-spaces in specified line range will not be listed.
3372 with only white-spaces in specified line range will not be listed.
3374
3373
3375 .. container:: verbose
3374 .. container:: verbose
3376
3375
3377 Some examples:
3376 Some examples:
3378
3377
3379 - changesets with full descriptions and file lists::
3378 - changesets with full descriptions and file lists::
3380
3379
3381 hg log -v
3380 hg log -v
3382
3381
3383 - changesets ancestral to the working directory::
3382 - changesets ancestral to the working directory::
3384
3383
3385 hg log -f
3384 hg log -f
3386
3385
3387 - last 10 commits on the current branch::
3386 - last 10 commits on the current branch::
3388
3387
3389 hg log -l 10 -b .
3388 hg log -l 10 -b .
3390
3389
3391 - changesets showing all modifications of a file, including removals::
3390 - changesets showing all modifications of a file, including removals::
3392
3391
3393 hg log --removed file.c
3392 hg log --removed file.c
3394
3393
3395 - all changesets that touch a directory, with diffs, excluding merges::
3394 - all changesets that touch a directory, with diffs, excluding merges::
3396
3395
3397 hg log -Mp lib/
3396 hg log -Mp lib/
3398
3397
3399 - all revision numbers that match a keyword::
3398 - all revision numbers that match a keyword::
3400
3399
3401 hg log -k bug --template "{rev}\\n"
3400 hg log -k bug --template "{rev}\\n"
3402
3401
3403 - the full hash identifier of the working directory parent::
3402 - the full hash identifier of the working directory parent::
3404
3403
3405 hg log -r . --template "{node}\\n"
3404 hg log -r . --template "{node}\\n"
3406
3405
3407 - list available log templates::
3406 - list available log templates::
3408
3407
3409 hg log -T list
3408 hg log -T list
3410
3409
3411 - check if a given changeset is included in a tagged release::
3410 - check if a given changeset is included in a tagged release::
3412
3411
3413 hg log -r "a21ccf and ancestor(1.9)"
3412 hg log -r "a21ccf and ancestor(1.9)"
3414
3413
3415 - find all changesets by some user in a date range::
3414 - find all changesets by some user in a date range::
3416
3415
3417 hg log -k alice -d "may 2008 to jul 2008"
3416 hg log -k alice -d "may 2008 to jul 2008"
3418
3417
3419 - summary of all changesets after the last tag::
3418 - summary of all changesets after the last tag::
3420
3419
3421 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3420 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3422
3421
3423 - changesets touching lines 13 to 23 for file.c::
3422 - changesets touching lines 13 to 23 for file.c::
3424
3423
3425 hg log -L file.c,13:23
3424 hg log -L file.c,13:23
3426
3425
3427 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3426 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3428 main.c with patch::
3427 main.c with patch::
3429
3428
3430 hg log -L file.c,13:23 -L main.c,2:6 -p
3429 hg log -L file.c,13:23 -L main.c,2:6 -p
3431
3430
3432 See :hg:`help dates` for a list of formats valid for -d/--date.
3431 See :hg:`help dates` for a list of formats valid for -d/--date.
3433
3432
3434 See :hg:`help revisions` for more about specifying and ordering
3433 See :hg:`help revisions` for more about specifying and ordering
3435 revisions.
3434 revisions.
3436
3435
3437 See :hg:`help templates` for more about pre-packaged styles and
3436 See :hg:`help templates` for more about pre-packaged styles and
3438 specifying custom templates. The default template used by the log
3437 specifying custom templates. The default template used by the log
3439 command can be customized via the ``ui.logtemplate`` configuration
3438 command can be customized via the ``ui.logtemplate`` configuration
3440 setting.
3439 setting.
3441
3440
3442 Returns 0 on success.
3441 Returns 0 on success.
3443
3442
3444 """
3443 """
3445 opts = pycompat.byteskwargs(opts)
3444 opts = pycompat.byteskwargs(opts)
3446 linerange = opts.get('line_range')
3445 linerange = opts.get('line_range')
3447
3446
3448 if linerange and not opts.get('follow'):
3447 if linerange and not opts.get('follow'):
3449 raise error.Abort(_('--line-range requires --follow'))
3448 raise error.Abort(_('--line-range requires --follow'))
3450
3449
3451 if linerange and pats:
3450 if linerange and pats:
3452 # TODO: take pats as patterns with no line-range filter
3451 # TODO: take pats as patterns with no line-range filter
3453 raise error.Abort(
3452 raise error.Abort(
3454 _('FILE arguments are not compatible with --line-range option')
3453 _('FILE arguments are not compatible with --line-range option')
3455 )
3454 )
3456
3455
3457 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3456 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3458 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3457 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3459 if linerange:
3458 if linerange:
3460 # TODO: should follow file history from logcmdutil._initialrevs(),
3459 # TODO: should follow file history from logcmdutil._initialrevs(),
3461 # then filter the result by logcmdutil._makerevset() and --limit
3460 # then filter the result by logcmdutil._makerevset() and --limit
3462 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3461 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3463
3462
3464 getrenamed = None
3463 getrenamed = None
3465 if opts.get('copies'):
3464 if opts.get('copies'):
3466 endrev = None
3465 endrev = None
3467 if opts.get('rev'):
3466 if opts.get('rev'):
3468 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
3467 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
3469 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3468 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3470
3469
3471 ui.pager('log')
3470 ui.pager('log')
3472 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3471 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3473 buffered=True)
3472 buffered=True)
3474 if opts.get('graph'):
3473 if opts.get('graph'):
3475 displayfn = logcmdutil.displaygraphrevs
3474 displayfn = logcmdutil.displaygraphrevs
3476 else:
3475 else:
3477 displayfn = logcmdutil.displayrevs
3476 displayfn = logcmdutil.displayrevs
3478 displayfn(ui, repo, revs, displayer, getrenamed)
3477 displayfn(ui, repo, revs, displayer, getrenamed)
3479
3478
3480 @command('manifest',
3479 @command('manifest',
3481 [('r', 'rev', '', _('revision to display'), _('REV')),
3480 [('r', 'rev', '', _('revision to display'), _('REV')),
3482 ('', 'all', False, _("list files from all revisions"))]
3481 ('', 'all', False, _("list files from all revisions"))]
3483 + formatteropts,
3482 + formatteropts,
3484 _('[-r REV]'), cmdtype=readonly)
3483 _('[-r REV]'), cmdtype=readonly)
3485 def manifest(ui, repo, node=None, rev=None, **opts):
3484 def manifest(ui, repo, node=None, rev=None, **opts):
3486 """output the current or given revision of the project manifest
3485 """output the current or given revision of the project manifest
3487
3486
3488 Print a list of version controlled files for the given revision.
3487 Print a list of version controlled files for the given revision.
3489 If no revision is given, the first parent of the working directory
3488 If no revision is given, the first parent of the working directory
3490 is used, or the null revision if no revision is checked out.
3489 is used, or the null revision if no revision is checked out.
3491
3490
3492 With -v, print file permissions, symlink and executable bits.
3491 With -v, print file permissions, symlink and executable bits.
3493 With --debug, print file revision hashes.
3492 With --debug, print file revision hashes.
3494
3493
3495 If option --all is specified, the list of all files from all revisions
3494 If option --all is specified, the list of all files from all revisions
3496 is printed. This includes deleted and renamed files.
3495 is printed. This includes deleted and renamed files.
3497
3496
3498 Returns 0 on success.
3497 Returns 0 on success.
3499 """
3498 """
3500 opts = pycompat.byteskwargs(opts)
3499 opts = pycompat.byteskwargs(opts)
3501 fm = ui.formatter('manifest', opts)
3500 fm = ui.formatter('manifest', opts)
3502
3501
3503 if opts.get('all'):
3502 if opts.get('all'):
3504 if rev or node:
3503 if rev or node:
3505 raise error.Abort(_("can't specify a revision with --all"))
3504 raise error.Abort(_("can't specify a revision with --all"))
3506
3505
3507 res = set()
3506 res = set()
3508 for rev in repo:
3507 for rev in repo:
3509 ctx = repo[rev]
3508 ctx = repo[rev]
3510 res |= set(ctx.files())
3509 res |= set(ctx.files())
3511
3510
3512 ui.pager('manifest')
3511 ui.pager('manifest')
3513 for f in sorted(res):
3512 for f in sorted(res):
3514 fm.startitem()
3513 fm.startitem()
3515 fm.write("path", '%s\n', f)
3514 fm.write("path", '%s\n', f)
3516 fm.end()
3515 fm.end()
3517 return
3516 return
3518
3517
3519 if rev and node:
3518 if rev and node:
3520 raise error.Abort(_("please specify just one revision"))
3519 raise error.Abort(_("please specify just one revision"))
3521
3520
3522 if not node:
3521 if not node:
3523 node = rev
3522 node = rev
3524
3523
3525 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3524 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3526 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3525 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3527 if node:
3526 if node:
3528 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3527 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3529 ctx = scmutil.revsingle(repo, node)
3528 ctx = scmutil.revsingle(repo, node)
3530 mf = ctx.manifest()
3529 mf = ctx.manifest()
3531 ui.pager('manifest')
3530 ui.pager('manifest')
3532 for f in ctx:
3531 for f in ctx:
3533 fm.startitem()
3532 fm.startitem()
3534 fl = ctx[f].flags()
3533 fl = ctx[f].flags()
3535 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3534 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3536 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3535 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3537 fm.write('path', '%s\n', f)
3536 fm.write('path', '%s\n', f)
3538 fm.end()
3537 fm.end()
3539
3538
3540 @command('^merge',
3539 @command('^merge',
3541 [('f', 'force', None,
3540 [('f', 'force', None,
3542 _('force a merge including outstanding changes (DEPRECATED)')),
3541 _('force a merge including outstanding changes (DEPRECATED)')),
3543 ('r', 'rev', '', _('revision to merge'), _('REV')),
3542 ('r', 'rev', '', _('revision to merge'), _('REV')),
3544 ('P', 'preview', None,
3543 ('P', 'preview', None,
3545 _('review revisions to merge (no merge is performed)')),
3544 _('review revisions to merge (no merge is performed)')),
3546 ('', 'abort', None, _('abort the ongoing merge')),
3545 ('', 'abort', None, _('abort the ongoing merge')),
3547 ] + mergetoolopts,
3546 ] + mergetoolopts,
3548 _('[-P] [[-r] REV]'))
3547 _('[-P] [[-r] REV]'))
3549 def merge(ui, repo, node=None, **opts):
3548 def merge(ui, repo, node=None, **opts):
3550 """merge another revision into working directory
3549 """merge another revision into working directory
3551
3550
3552 The current working directory is updated with all changes made in
3551 The current working directory is updated with all changes made in
3553 the requested revision since the last common predecessor revision.
3552 the requested revision since the last common predecessor revision.
3554
3553
3555 Files that changed between either parent are marked as changed for
3554 Files that changed between either parent are marked as changed for
3556 the next commit and a commit must be performed before any further
3555 the next commit and a commit must be performed before any further
3557 updates to the repository are allowed. The next commit will have
3556 updates to the repository are allowed. The next commit will have
3558 two parents.
3557 two parents.
3559
3558
3560 ``--tool`` can be used to specify the merge tool used for file
3559 ``--tool`` can be used to specify the merge tool used for file
3561 merges. It overrides the HGMERGE environment variable and your
3560 merges. It overrides the HGMERGE environment variable and your
3562 configuration files. See :hg:`help merge-tools` for options.
3561 configuration files. See :hg:`help merge-tools` for options.
3563
3562
3564 If no revision is specified, the working directory's parent is a
3563 If no revision is specified, the working directory's parent is a
3565 head revision, and the current branch contains exactly one other
3564 head revision, and the current branch contains exactly one other
3566 head, the other head is merged with by default. Otherwise, an
3565 head, the other head is merged with by default. Otherwise, an
3567 explicit revision with which to merge with must be provided.
3566 explicit revision with which to merge with must be provided.
3568
3567
3569 See :hg:`help resolve` for information on handling file conflicts.
3568 See :hg:`help resolve` for information on handling file conflicts.
3570
3569
3571 To undo an uncommitted merge, use :hg:`merge --abort` which
3570 To undo an uncommitted merge, use :hg:`merge --abort` which
3572 will check out a clean copy of the original merge parent, losing
3571 will check out a clean copy of the original merge parent, losing
3573 all changes.
3572 all changes.
3574
3573
3575 Returns 0 on success, 1 if there are unresolved files.
3574 Returns 0 on success, 1 if there are unresolved files.
3576 """
3575 """
3577
3576
3578 opts = pycompat.byteskwargs(opts)
3577 opts = pycompat.byteskwargs(opts)
3579 abort = opts.get('abort')
3578 abort = opts.get('abort')
3580 if abort and repo.dirstate.p2() == nullid:
3579 if abort and repo.dirstate.p2() == nullid:
3581 cmdutil.wrongtooltocontinue(repo, _('merge'))
3580 cmdutil.wrongtooltocontinue(repo, _('merge'))
3582 if abort:
3581 if abort:
3583 if node:
3582 if node:
3584 raise error.Abort(_("cannot specify a node with --abort"))
3583 raise error.Abort(_("cannot specify a node with --abort"))
3585 if opts.get('rev'):
3584 if opts.get('rev'):
3586 raise error.Abort(_("cannot specify both --rev and --abort"))
3585 raise error.Abort(_("cannot specify both --rev and --abort"))
3587 if opts.get('preview'):
3586 if opts.get('preview'):
3588 raise error.Abort(_("cannot specify --preview with --abort"))
3587 raise error.Abort(_("cannot specify --preview with --abort"))
3589 if opts.get('rev') and node:
3588 if opts.get('rev') and node:
3590 raise error.Abort(_("please specify just one revision"))
3589 raise error.Abort(_("please specify just one revision"))
3591 if not node:
3590 if not node:
3592 node = opts.get('rev')
3591 node = opts.get('rev')
3593
3592
3594 if node:
3593 if node:
3595 node = scmutil.revsingle(repo, node).node()
3594 node = scmutil.revsingle(repo, node).node()
3596
3595
3597 if not node and not abort:
3596 if not node and not abort:
3598 node = repo[destutil.destmerge(repo)].node()
3597 node = repo[destutil.destmerge(repo)].node()
3599
3598
3600 if opts.get('preview'):
3599 if opts.get('preview'):
3601 # find nodes that are ancestors of p2 but not of p1
3600 # find nodes that are ancestors of p2 but not of p1
3602 p1 = repo.lookup('.')
3601 p1 = repo.lookup('.')
3603 p2 = node
3602 p2 = node
3604 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3603 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3605
3604
3606 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3605 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3607 for node in nodes:
3606 for node in nodes:
3608 displayer.show(repo[node])
3607 displayer.show(repo[node])
3609 displayer.close()
3608 displayer.close()
3610 return 0
3609 return 0
3611
3610
3612 try:
3611 try:
3613 # ui.forcemerge is an internal variable, do not document
3612 # ui.forcemerge is an internal variable, do not document
3614 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
3613 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
3615 force = opts.get('force')
3614 force = opts.get('force')
3616 labels = ['working copy', 'merge rev']
3615 labels = ['working copy', 'merge rev']
3617 return hg.merge(repo, node, force=force, mergeforce=force,
3616 return hg.merge(repo, node, force=force, mergeforce=force,
3618 labels=labels, abort=abort)
3617 labels=labels, abort=abort)
3619 finally:
3618 finally:
3620 ui.setconfig('ui', 'forcemerge', '', 'merge')
3619 ui.setconfig('ui', 'forcemerge', '', 'merge')
3621
3620
3622 @command('outgoing|out',
3621 @command('outgoing|out',
3623 [('f', 'force', None, _('run even when the destination is unrelated')),
3622 [('f', 'force', None, _('run even when the destination is unrelated')),
3624 ('r', 'rev', [],
3623 ('r', 'rev', [],
3625 _('a changeset intended to be included in the destination'), _('REV')),
3624 _('a changeset intended to be included in the destination'), _('REV')),
3626 ('n', 'newest-first', None, _('show newest record first')),
3625 ('n', 'newest-first', None, _('show newest record first')),
3627 ('B', 'bookmarks', False, _('compare bookmarks')),
3626 ('B', 'bookmarks', False, _('compare bookmarks')),
3628 ('b', 'branch', [], _('a specific branch you would like to push'),
3627 ('b', 'branch', [], _('a specific branch you would like to push'),
3629 _('BRANCH')),
3628 _('BRANCH')),
3630 ] + logopts + remoteopts + subrepoopts,
3629 ] + logopts + remoteopts + subrepoopts,
3631 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3630 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3632 def outgoing(ui, repo, dest=None, **opts):
3631 def outgoing(ui, repo, dest=None, **opts):
3633 """show changesets not found in the destination
3632 """show changesets not found in the destination
3634
3633
3635 Show changesets not found in the specified destination repository
3634 Show changesets not found in the specified destination repository
3636 or the default push location. These are the changesets that would
3635 or the default push location. These are the changesets that would
3637 be pushed if a push was requested.
3636 be pushed if a push was requested.
3638
3637
3639 See pull for details of valid destination formats.
3638 See pull for details of valid destination formats.
3640
3639
3641 .. container:: verbose
3640 .. container:: verbose
3642
3641
3643 With -B/--bookmarks, the result of bookmark comparison between
3642 With -B/--bookmarks, the result of bookmark comparison between
3644 local and remote repositories is displayed. With -v/--verbose,
3643 local and remote repositories is displayed. With -v/--verbose,
3645 status is also displayed for each bookmark like below::
3644 status is also displayed for each bookmark like below::
3646
3645
3647 BM1 01234567890a added
3646 BM1 01234567890a added
3648 BM2 deleted
3647 BM2 deleted
3649 BM3 234567890abc advanced
3648 BM3 234567890abc advanced
3650 BM4 34567890abcd diverged
3649 BM4 34567890abcd diverged
3651 BM5 4567890abcde changed
3650 BM5 4567890abcde changed
3652
3651
3653 The action taken when pushing depends on the
3652 The action taken when pushing depends on the
3654 status of each bookmark:
3653 status of each bookmark:
3655
3654
3656 :``added``: push with ``-B`` will create it
3655 :``added``: push with ``-B`` will create it
3657 :``deleted``: push with ``-B`` will delete it
3656 :``deleted``: push with ``-B`` will delete it
3658 :``advanced``: push will update it
3657 :``advanced``: push will update it
3659 :``diverged``: push with ``-B`` will update it
3658 :``diverged``: push with ``-B`` will update it
3660 :``changed``: push with ``-B`` will update it
3659 :``changed``: push with ``-B`` will update it
3661
3660
3662 From the point of view of pushing behavior, bookmarks
3661 From the point of view of pushing behavior, bookmarks
3663 existing only in the remote repository are treated as
3662 existing only in the remote repository are treated as
3664 ``deleted``, even if it is in fact added remotely.
3663 ``deleted``, even if it is in fact added remotely.
3665
3664
3666 Returns 0 if there are outgoing changes, 1 otherwise.
3665 Returns 0 if there are outgoing changes, 1 otherwise.
3667 """
3666 """
3668 opts = pycompat.byteskwargs(opts)
3667 opts = pycompat.byteskwargs(opts)
3669 if opts.get('graph'):
3668 if opts.get('graph'):
3670 logcmdutil.checkunsupportedgraphflags([], opts)
3669 logcmdutil.checkunsupportedgraphflags([], opts)
3671 o, other = hg._outgoing(ui, repo, dest, opts)
3670 o, other = hg._outgoing(ui, repo, dest, opts)
3672 if not o:
3671 if not o:
3673 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3672 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3674 return
3673 return
3675
3674
3676 revdag = logcmdutil.graphrevs(repo, o, opts)
3675 revdag = logcmdutil.graphrevs(repo, o, opts)
3677 ui.pager('outgoing')
3676 ui.pager('outgoing')
3678 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
3677 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
3679 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3678 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3680 graphmod.asciiedges)
3679 graphmod.asciiedges)
3681 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3680 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3682 return 0
3681 return 0
3683
3682
3684 if opts.get('bookmarks'):
3683 if opts.get('bookmarks'):
3685 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3684 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3686 dest, branches = hg.parseurl(dest, opts.get('branch'))
3685 dest, branches = hg.parseurl(dest, opts.get('branch'))
3687 other = hg.peer(repo, opts, dest)
3686 other = hg.peer(repo, opts, dest)
3688 if 'bookmarks' not in other.listkeys('namespaces'):
3687 if 'bookmarks' not in other.listkeys('namespaces'):
3689 ui.warn(_("remote doesn't support bookmarks\n"))
3688 ui.warn(_("remote doesn't support bookmarks\n"))
3690 return 0
3689 return 0
3691 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3690 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3692 ui.pager('outgoing')
3691 ui.pager('outgoing')
3693 return bookmarks.outgoing(ui, repo, other)
3692 return bookmarks.outgoing(ui, repo, other)
3694
3693
3695 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3694 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3696 try:
3695 try:
3697 return hg.outgoing(ui, repo, dest, opts)
3696 return hg.outgoing(ui, repo, dest, opts)
3698 finally:
3697 finally:
3699 del repo._subtoppath
3698 del repo._subtoppath
3700
3699
3701 @command('parents',
3700 @command('parents',
3702 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3701 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3703 ] + templateopts,
3702 ] + templateopts,
3704 _('[-r REV] [FILE]'),
3703 _('[-r REV] [FILE]'),
3705 inferrepo=True)
3704 inferrepo=True)
3706 def parents(ui, repo, file_=None, **opts):
3705 def parents(ui, repo, file_=None, **opts):
3707 """show the parents of the working directory or revision (DEPRECATED)
3706 """show the parents of the working directory or revision (DEPRECATED)
3708
3707
3709 Print the working directory's parent revisions. If a revision is
3708 Print the working directory's parent revisions. If a revision is
3710 given via -r/--rev, the parent of that revision will be printed.
3709 given via -r/--rev, the parent of that revision will be printed.
3711 If a file argument is given, the revision in which the file was
3710 If a file argument is given, the revision in which the file was
3712 last changed (before the working directory revision or the
3711 last changed (before the working directory revision or the
3713 argument to --rev if given) is printed.
3712 argument to --rev if given) is printed.
3714
3713
3715 This command is equivalent to::
3714 This command is equivalent to::
3716
3715
3717 hg log -r "p1()+p2()" or
3716 hg log -r "p1()+p2()" or
3718 hg log -r "p1(REV)+p2(REV)" or
3717 hg log -r "p1(REV)+p2(REV)" or
3719 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3718 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3720 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3719 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3721
3720
3722 See :hg:`summary` and :hg:`help revsets` for related information.
3721 See :hg:`summary` and :hg:`help revsets` for related information.
3723
3722
3724 Returns 0 on success.
3723 Returns 0 on success.
3725 """
3724 """
3726
3725
3727 opts = pycompat.byteskwargs(opts)
3726 opts = pycompat.byteskwargs(opts)
3728 rev = opts.get('rev')
3727 rev = opts.get('rev')
3729 if rev:
3728 if rev:
3730 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3729 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3731 ctx = scmutil.revsingle(repo, rev, None)
3730 ctx = scmutil.revsingle(repo, rev, None)
3732
3731
3733 if file_:
3732 if file_:
3734 m = scmutil.match(ctx, (file_,), opts)
3733 m = scmutil.match(ctx, (file_,), opts)
3735 if m.anypats() or len(m.files()) != 1:
3734 if m.anypats() or len(m.files()) != 1:
3736 raise error.Abort(_('can only specify an explicit filename'))
3735 raise error.Abort(_('can only specify an explicit filename'))
3737 file_ = m.files()[0]
3736 file_ = m.files()[0]
3738 filenodes = []
3737 filenodes = []
3739 for cp in ctx.parents():
3738 for cp in ctx.parents():
3740 if not cp:
3739 if not cp:
3741 continue
3740 continue
3742 try:
3741 try:
3743 filenodes.append(cp.filenode(file_))
3742 filenodes.append(cp.filenode(file_))
3744 except error.LookupError:
3743 except error.LookupError:
3745 pass
3744 pass
3746 if not filenodes:
3745 if not filenodes:
3747 raise error.Abort(_("'%s' not found in manifest!") % file_)
3746 raise error.Abort(_("'%s' not found in manifest!") % file_)
3748 p = []
3747 p = []
3749 for fn in filenodes:
3748 for fn in filenodes:
3750 fctx = repo.filectx(file_, fileid=fn)
3749 fctx = repo.filectx(file_, fileid=fn)
3751 p.append(fctx.node())
3750 p.append(fctx.node())
3752 else:
3751 else:
3753 p = [cp.node() for cp in ctx.parents()]
3752 p = [cp.node() for cp in ctx.parents()]
3754
3753
3755 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3754 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3756 for n in p:
3755 for n in p:
3757 if n != nullid:
3756 if n != nullid:
3758 displayer.show(repo[n])
3757 displayer.show(repo[n])
3759 displayer.close()
3758 displayer.close()
3760
3759
3761 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True,
3760 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True,
3762 cmdtype=readonly)
3761 cmdtype=readonly)
3763 def paths(ui, repo, search=None, **opts):
3762 def paths(ui, repo, search=None, **opts):
3764 """show aliases for remote repositories
3763 """show aliases for remote repositories
3765
3764
3766 Show definition of symbolic path name NAME. If no name is given,
3765 Show definition of symbolic path name NAME. If no name is given,
3767 show definition of all available names.
3766 show definition of all available names.
3768
3767
3769 Option -q/--quiet suppresses all output when searching for NAME
3768 Option -q/--quiet suppresses all output when searching for NAME
3770 and shows only the path names when listing all definitions.
3769 and shows only the path names when listing all definitions.
3771
3770
3772 Path names are defined in the [paths] section of your
3771 Path names are defined in the [paths] section of your
3773 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3772 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3774 repository, ``.hg/hgrc`` is used, too.
3773 repository, ``.hg/hgrc`` is used, too.
3775
3774
3776 The path names ``default`` and ``default-push`` have a special
3775 The path names ``default`` and ``default-push`` have a special
3777 meaning. When performing a push or pull operation, they are used
3776 meaning. When performing a push or pull operation, they are used
3778 as fallbacks if no location is specified on the command-line.
3777 as fallbacks if no location is specified on the command-line.
3779 When ``default-push`` is set, it will be used for push and
3778 When ``default-push`` is set, it will be used for push and
3780 ``default`` will be used for pull; otherwise ``default`` is used
3779 ``default`` will be used for pull; otherwise ``default`` is used
3781 as the fallback for both. When cloning a repository, the clone
3780 as the fallback for both. When cloning a repository, the clone
3782 source is written as ``default`` in ``.hg/hgrc``.
3781 source is written as ``default`` in ``.hg/hgrc``.
3783
3782
3784 .. note::
3783 .. note::
3785
3784
3786 ``default`` and ``default-push`` apply to all inbound (e.g.
3785 ``default`` and ``default-push`` apply to all inbound (e.g.
3787 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3786 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3788 and :hg:`bundle`) operations.
3787 and :hg:`bundle`) operations.
3789
3788
3790 See :hg:`help urls` for more information.
3789 See :hg:`help urls` for more information.
3791
3790
3792 Returns 0 on success.
3791 Returns 0 on success.
3793 """
3792 """
3794
3793
3795 opts = pycompat.byteskwargs(opts)
3794 opts = pycompat.byteskwargs(opts)
3796 ui.pager('paths')
3795 ui.pager('paths')
3797 if search:
3796 if search:
3798 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3797 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3799 if name == search]
3798 if name == search]
3800 else:
3799 else:
3801 pathitems = sorted(ui.paths.iteritems())
3800 pathitems = sorted(ui.paths.iteritems())
3802
3801
3803 fm = ui.formatter('paths', opts)
3802 fm = ui.formatter('paths', opts)
3804 if fm.isplain():
3803 if fm.isplain():
3805 hidepassword = util.hidepassword
3804 hidepassword = util.hidepassword
3806 else:
3805 else:
3807 hidepassword = bytes
3806 hidepassword = bytes
3808 if ui.quiet:
3807 if ui.quiet:
3809 namefmt = '%s\n'
3808 namefmt = '%s\n'
3810 else:
3809 else:
3811 namefmt = '%s = '
3810 namefmt = '%s = '
3812 showsubopts = not search and not ui.quiet
3811 showsubopts = not search and not ui.quiet
3813
3812
3814 for name, path in pathitems:
3813 for name, path in pathitems:
3815 fm.startitem()
3814 fm.startitem()
3816 fm.condwrite(not search, 'name', namefmt, name)
3815 fm.condwrite(not search, 'name', namefmt, name)
3817 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3816 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3818 for subopt, value in sorted(path.suboptions.items()):
3817 for subopt, value in sorted(path.suboptions.items()):
3819 assert subopt not in ('name', 'url')
3818 assert subopt not in ('name', 'url')
3820 if showsubopts:
3819 if showsubopts:
3821 fm.plain('%s:%s = ' % (name, subopt))
3820 fm.plain('%s:%s = ' % (name, subopt))
3822 fm.condwrite(showsubopts, subopt, '%s\n', value)
3821 fm.condwrite(showsubopts, subopt, '%s\n', value)
3823
3822
3824 fm.end()
3823 fm.end()
3825
3824
3826 if search and not pathitems:
3825 if search and not pathitems:
3827 if not ui.quiet:
3826 if not ui.quiet:
3828 ui.warn(_("not found!\n"))
3827 ui.warn(_("not found!\n"))
3829 return 1
3828 return 1
3830 else:
3829 else:
3831 return 0
3830 return 0
3832
3831
3833 @command('phase',
3832 @command('phase',
3834 [('p', 'public', False, _('set changeset phase to public')),
3833 [('p', 'public', False, _('set changeset phase to public')),
3835 ('d', 'draft', False, _('set changeset phase to draft')),
3834 ('d', 'draft', False, _('set changeset phase to draft')),
3836 ('s', 'secret', False, _('set changeset phase to secret')),
3835 ('s', 'secret', False, _('set changeset phase to secret')),
3837 ('f', 'force', False, _('allow to move boundary backward')),
3836 ('f', 'force', False, _('allow to move boundary backward')),
3838 ('r', 'rev', [], _('target revision'), _('REV')),
3837 ('r', 'rev', [], _('target revision'), _('REV')),
3839 ],
3838 ],
3840 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3839 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3841 def phase(ui, repo, *revs, **opts):
3840 def phase(ui, repo, *revs, **opts):
3842 """set or show the current phase name
3841 """set or show the current phase name
3843
3842
3844 With no argument, show the phase name of the current revision(s).
3843 With no argument, show the phase name of the current revision(s).
3845
3844
3846 With one of -p/--public, -d/--draft or -s/--secret, change the
3845 With one of -p/--public, -d/--draft or -s/--secret, change the
3847 phase value of the specified revisions.
3846 phase value of the specified revisions.
3848
3847
3849 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
3848 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
3850 lower phase to a higher phase. Phases are ordered as follows::
3849 lower phase to a higher phase. Phases are ordered as follows::
3851
3850
3852 public < draft < secret
3851 public < draft < secret
3853
3852
3854 Returns 0 on success, 1 if some phases could not be changed.
3853 Returns 0 on success, 1 if some phases could not be changed.
3855
3854
3856 (For more information about the phases concept, see :hg:`help phases`.)
3855 (For more information about the phases concept, see :hg:`help phases`.)
3857 """
3856 """
3858 opts = pycompat.byteskwargs(opts)
3857 opts = pycompat.byteskwargs(opts)
3859 # search for a unique phase argument
3858 # search for a unique phase argument
3860 targetphase = None
3859 targetphase = None
3861 for idx, name in enumerate(phases.phasenames):
3860 for idx, name in enumerate(phases.phasenames):
3862 if opts[name]:
3861 if opts[name]:
3863 if targetphase is not None:
3862 if targetphase is not None:
3864 raise error.Abort(_('only one phase can be specified'))
3863 raise error.Abort(_('only one phase can be specified'))
3865 targetphase = idx
3864 targetphase = idx
3866
3865
3867 # look for specified revision
3866 # look for specified revision
3868 revs = list(revs)
3867 revs = list(revs)
3869 revs.extend(opts['rev'])
3868 revs.extend(opts['rev'])
3870 if not revs:
3869 if not revs:
3871 # display both parents as the second parent phase can influence
3870 # display both parents as the second parent phase can influence
3872 # the phase of a merge commit
3871 # the phase of a merge commit
3873 revs = [c.rev() for c in repo[None].parents()]
3872 revs = [c.rev() for c in repo[None].parents()]
3874
3873
3875 revs = scmutil.revrange(repo, revs)
3874 revs = scmutil.revrange(repo, revs)
3876
3875
3877 ret = 0
3876 ret = 0
3878 if targetphase is None:
3877 if targetphase is None:
3879 # display
3878 # display
3880 for r in revs:
3879 for r in revs:
3881 ctx = repo[r]
3880 ctx = repo[r]
3882 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
3881 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
3883 else:
3882 else:
3884 with repo.lock(), repo.transaction("phase") as tr:
3883 with repo.lock(), repo.transaction("phase") as tr:
3885 # set phase
3884 # set phase
3886 if not revs:
3885 if not revs:
3887 raise error.Abort(_('empty revision set'))
3886 raise error.Abort(_('empty revision set'))
3888 nodes = [repo[r].node() for r in revs]
3887 nodes = [repo[r].node() for r in revs]
3889 # moving revision from public to draft may hide them
3888 # moving revision from public to draft may hide them
3890 # We have to check result on an unfiltered repository
3889 # We have to check result on an unfiltered repository
3891 unfi = repo.unfiltered()
3890 unfi = repo.unfiltered()
3892 getphase = unfi._phasecache.phase
3891 getphase = unfi._phasecache.phase
3893 olddata = [getphase(unfi, r) for r in unfi]
3892 olddata = [getphase(unfi, r) for r in unfi]
3894 phases.advanceboundary(repo, tr, targetphase, nodes)
3893 phases.advanceboundary(repo, tr, targetphase, nodes)
3895 if opts['force']:
3894 if opts['force']:
3896 phases.retractboundary(repo, tr, targetphase, nodes)
3895 phases.retractboundary(repo, tr, targetphase, nodes)
3897 getphase = unfi._phasecache.phase
3896 getphase = unfi._phasecache.phase
3898 newdata = [getphase(unfi, r) for r in unfi]
3897 newdata = [getphase(unfi, r) for r in unfi]
3899 changes = sum(newdata[r] != olddata[r] for r in unfi)
3898 changes = sum(newdata[r] != olddata[r] for r in unfi)
3900 cl = unfi.changelog
3899 cl = unfi.changelog
3901 rejected = [n for n in nodes
3900 rejected = [n for n in nodes
3902 if newdata[cl.rev(n)] < targetphase]
3901 if newdata[cl.rev(n)] < targetphase]
3903 if rejected:
3902 if rejected:
3904 ui.warn(_('cannot move %i changesets to a higher '
3903 ui.warn(_('cannot move %i changesets to a higher '
3905 'phase, use --force\n') % len(rejected))
3904 'phase, use --force\n') % len(rejected))
3906 ret = 1
3905 ret = 1
3907 if changes:
3906 if changes:
3908 msg = _('phase changed for %i changesets\n') % changes
3907 msg = _('phase changed for %i changesets\n') % changes
3909 if ret:
3908 if ret:
3910 ui.status(msg)
3909 ui.status(msg)
3911 else:
3910 else:
3912 ui.note(msg)
3911 ui.note(msg)
3913 else:
3912 else:
3914 ui.warn(_('no phases changed\n'))
3913 ui.warn(_('no phases changed\n'))
3915 return ret
3914 return ret
3916
3915
3917 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
3916 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
3918 """Run after a changegroup has been added via pull/unbundle
3917 """Run after a changegroup has been added via pull/unbundle
3919
3918
3920 This takes arguments below:
3919 This takes arguments below:
3921
3920
3922 :modheads: change of heads by pull/unbundle
3921 :modheads: change of heads by pull/unbundle
3923 :optupdate: updating working directory is needed or not
3922 :optupdate: updating working directory is needed or not
3924 :checkout: update destination revision (or None to default destination)
3923 :checkout: update destination revision (or None to default destination)
3925 :brev: a name, which might be a bookmark to be activated after updating
3924 :brev: a name, which might be a bookmark to be activated after updating
3926 """
3925 """
3927 if modheads == 0:
3926 if modheads == 0:
3928 return
3927 return
3929 if optupdate:
3928 if optupdate:
3930 try:
3929 try:
3931 return hg.updatetotally(ui, repo, checkout, brev)
3930 return hg.updatetotally(ui, repo, checkout, brev)
3932 except error.UpdateAbort as inst:
3931 except error.UpdateAbort as inst:
3933 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
3932 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
3934 hint = inst.hint
3933 hint = inst.hint
3935 raise error.UpdateAbort(msg, hint=hint)
3934 raise error.UpdateAbort(msg, hint=hint)
3936 if modheads > 1:
3935 if modheads > 1:
3937 currentbranchheads = len(repo.branchheads())
3936 currentbranchheads = len(repo.branchheads())
3938 if currentbranchheads == modheads:
3937 if currentbranchheads == modheads:
3939 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3938 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3940 elif currentbranchheads > 1:
3939 elif currentbranchheads > 1:
3941 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
3940 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
3942 "merge)\n"))
3941 "merge)\n"))
3943 else:
3942 else:
3944 ui.status(_("(run 'hg heads' to see heads)\n"))
3943 ui.status(_("(run 'hg heads' to see heads)\n"))
3945 elif not ui.configbool('commands', 'update.requiredest'):
3944 elif not ui.configbool('commands', 'update.requiredest'):
3946 ui.status(_("(run 'hg update' to get a working copy)\n"))
3945 ui.status(_("(run 'hg update' to get a working copy)\n"))
3947
3946
3948 @command('^pull',
3947 @command('^pull',
3949 [('u', 'update', None,
3948 [('u', 'update', None,
3950 _('update to new branch head if new descendants were pulled')),
3949 _('update to new branch head if new descendants were pulled')),
3951 ('f', 'force', None, _('run even when remote repository is unrelated')),
3950 ('f', 'force', None, _('run even when remote repository is unrelated')),
3952 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3951 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3953 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3952 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3954 ('b', 'branch', [], _('a specific branch you would like to pull'),
3953 ('b', 'branch', [], _('a specific branch you would like to pull'),
3955 _('BRANCH')),
3954 _('BRANCH')),
3956 ] + remoteopts,
3955 ] + remoteopts,
3957 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3956 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3958 def pull(ui, repo, source="default", **opts):
3957 def pull(ui, repo, source="default", **opts):
3959 """pull changes from the specified source
3958 """pull changes from the specified source
3960
3959
3961 Pull changes from a remote repository to a local one.
3960 Pull changes from a remote repository to a local one.
3962
3961
3963 This finds all changes from the repository at the specified path
3962 This finds all changes from the repository at the specified path
3964 or URL and adds them to a local repository (the current one unless
3963 or URL and adds them to a local repository (the current one unless
3965 -R is specified). By default, this does not update the copy of the
3964 -R is specified). By default, this does not update the copy of the
3966 project in the working directory.
3965 project in the working directory.
3967
3966
3968 When cloning from servers that support it, Mercurial may fetch
3967 When cloning from servers that support it, Mercurial may fetch
3969 pre-generated data. When this is done, hooks operating on incoming
3968 pre-generated data. When this is done, hooks operating on incoming
3970 changesets and changegroups may fire more than once, once for each
3969 changesets and changegroups may fire more than once, once for each
3971 pre-generated bundle and as well as for any additional remaining
3970 pre-generated bundle and as well as for any additional remaining
3972 data. See :hg:`help -e clonebundles` for more.
3971 data. See :hg:`help -e clonebundles` for more.
3973
3972
3974 Use :hg:`incoming` if you want to see what would have been added
3973 Use :hg:`incoming` if you want to see what would have been added
3975 by a pull at the time you issued this command. If you then decide
3974 by a pull at the time you issued this command. If you then decide
3976 to add those changes to the repository, you should use :hg:`pull
3975 to add those changes to the repository, you should use :hg:`pull
3977 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3976 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3978
3977
3979 If SOURCE is omitted, the 'default' path will be used.
3978 If SOURCE is omitted, the 'default' path will be used.
3980 See :hg:`help urls` for more information.
3979 See :hg:`help urls` for more information.
3981
3980
3982 Specifying bookmark as ``.`` is equivalent to specifying the active
3981 Specifying bookmark as ``.`` is equivalent to specifying the active
3983 bookmark's name.
3982 bookmark's name.
3984
3983
3985 Returns 0 on success, 1 if an update had unresolved files.
3984 Returns 0 on success, 1 if an update had unresolved files.
3986 """
3985 """
3987
3986
3988 opts = pycompat.byteskwargs(opts)
3987 opts = pycompat.byteskwargs(opts)
3989 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
3988 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
3990 msg = _('update destination required by configuration')
3989 msg = _('update destination required by configuration')
3991 hint = _('use hg pull followed by hg update DEST')
3990 hint = _('use hg pull followed by hg update DEST')
3992 raise error.Abort(msg, hint=hint)
3991 raise error.Abort(msg, hint=hint)
3993
3992
3994 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3993 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3995 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3994 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3996 other = hg.peer(repo, opts, source)
3995 other = hg.peer(repo, opts, source)
3997 try:
3996 try:
3998 revs, checkout = hg.addbranchrevs(repo, other, branches,
3997 revs, checkout = hg.addbranchrevs(repo, other, branches,
3999 opts.get('rev'))
3998 opts.get('rev'))
4000
3999
4001
4000
4002 pullopargs = {}
4001 pullopargs = {}
4003 if opts.get('bookmark'):
4002 if opts.get('bookmark'):
4004 if not revs:
4003 if not revs:
4005 revs = []
4004 revs = []
4006 # The list of bookmark used here is not the one used to actually
4005 # The list of bookmark used here is not the one used to actually
4007 # update the bookmark name. This can result in the revision pulled
4006 # update the bookmark name. This can result in the revision pulled
4008 # not ending up with the name of the bookmark because of a race
4007 # not ending up with the name of the bookmark because of a race
4009 # condition on the server. (See issue 4689 for details)
4008 # condition on the server. (See issue 4689 for details)
4010 remotebookmarks = other.listkeys('bookmarks')
4009 remotebookmarks = other.listkeys('bookmarks')
4011 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4010 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4012 pullopargs['remotebookmarks'] = remotebookmarks
4011 pullopargs['remotebookmarks'] = remotebookmarks
4013 for b in opts['bookmark']:
4012 for b in opts['bookmark']:
4014 b = repo._bookmarks.expandname(b)
4013 b = repo._bookmarks.expandname(b)
4015 if b not in remotebookmarks:
4014 if b not in remotebookmarks:
4016 raise error.Abort(_('remote bookmark %s not found!') % b)
4015 raise error.Abort(_('remote bookmark %s not found!') % b)
4017 revs.append(hex(remotebookmarks[b]))
4016 revs.append(hex(remotebookmarks[b]))
4018
4017
4019 if revs:
4018 if revs:
4020 try:
4019 try:
4021 # When 'rev' is a bookmark name, we cannot guarantee that it
4020 # When 'rev' is a bookmark name, we cannot guarantee that it
4022 # will be updated with that name because of a race condition
4021 # will be updated with that name because of a race condition
4023 # server side. (See issue 4689 for details)
4022 # server side. (See issue 4689 for details)
4024 oldrevs = revs
4023 oldrevs = revs
4025 revs = [] # actually, nodes
4024 revs = [] # actually, nodes
4026 for r in oldrevs:
4025 for r in oldrevs:
4027 node = other.lookup(r)
4026 node = other.lookup(r)
4028 revs.append(node)
4027 revs.append(node)
4029 if r == checkout:
4028 if r == checkout:
4030 checkout = node
4029 checkout = node
4031 except error.CapabilityError:
4030 except error.CapabilityError:
4032 err = _("other repository doesn't support revision lookup, "
4031 err = _("other repository doesn't support revision lookup, "
4033 "so a rev cannot be specified.")
4032 "so a rev cannot be specified.")
4034 raise error.Abort(err)
4033 raise error.Abort(err)
4035
4034
4036 wlock = util.nullcontextmanager()
4035 wlock = util.nullcontextmanager()
4037 if opts.get('update'):
4036 if opts.get('update'):
4038 wlock = repo.wlock()
4037 wlock = repo.wlock()
4039 with wlock:
4038 with wlock:
4040 pullopargs.update(opts.get('opargs', {}))
4039 pullopargs.update(opts.get('opargs', {}))
4041 modheads = exchange.pull(repo, other, heads=revs,
4040 modheads = exchange.pull(repo, other, heads=revs,
4042 force=opts.get('force'),
4041 force=opts.get('force'),
4043 bookmarks=opts.get('bookmark', ()),
4042 bookmarks=opts.get('bookmark', ()),
4044 opargs=pullopargs).cgresult
4043 opargs=pullopargs).cgresult
4045
4044
4046 # brev is a name, which might be a bookmark to be activated at
4045 # brev is a name, which might be a bookmark to be activated at
4047 # the end of the update. In other words, it is an explicit
4046 # the end of the update. In other words, it is an explicit
4048 # destination of the update
4047 # destination of the update
4049 brev = None
4048 brev = None
4050
4049
4051 if checkout:
4050 if checkout:
4052 checkout = repo.changelog.rev(checkout)
4051 checkout = repo.changelog.rev(checkout)
4053
4052
4054 # order below depends on implementation of
4053 # order below depends on implementation of
4055 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4054 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4056 # because 'checkout' is determined without it.
4055 # because 'checkout' is determined without it.
4057 if opts.get('rev'):
4056 if opts.get('rev'):
4058 brev = opts['rev'][0]
4057 brev = opts['rev'][0]
4059 elif opts.get('branch'):
4058 elif opts.get('branch'):
4060 brev = opts['branch'][0]
4059 brev = opts['branch'][0]
4061 else:
4060 else:
4062 brev = branches[0]
4061 brev = branches[0]
4063 repo._subtoppath = source
4062 repo._subtoppath = source
4064 try:
4063 try:
4065 ret = postincoming(ui, repo, modheads, opts.get('update'),
4064 ret = postincoming(ui, repo, modheads, opts.get('update'),
4066 checkout, brev)
4065 checkout, brev)
4067
4066
4068 finally:
4067 finally:
4069 del repo._subtoppath
4068 del repo._subtoppath
4070
4069
4071 finally:
4070 finally:
4072 other.close()
4071 other.close()
4073 return ret
4072 return ret
4074
4073
4075 @command('^push',
4074 @command('^push',
4076 [('f', 'force', None, _('force push')),
4075 [('f', 'force', None, _('force push')),
4077 ('r', 'rev', [],
4076 ('r', 'rev', [],
4078 _('a changeset intended to be included in the destination'),
4077 _('a changeset intended to be included in the destination'),
4079 _('REV')),
4078 _('REV')),
4080 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4079 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4081 ('b', 'branch', [],
4080 ('b', 'branch', [],
4082 _('a specific branch you would like to push'), _('BRANCH')),
4081 _('a specific branch you would like to push'), _('BRANCH')),
4083 ('', 'new-branch', False, _('allow pushing a new branch')),
4082 ('', 'new-branch', False, _('allow pushing a new branch')),
4084 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4083 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4085 ] + remoteopts,
4084 ] + remoteopts,
4086 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4085 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4087 def push(ui, repo, dest=None, **opts):
4086 def push(ui, repo, dest=None, **opts):
4088 """push changes to the specified destination
4087 """push changes to the specified destination
4089
4088
4090 Push changesets from the local repository to the specified
4089 Push changesets from the local repository to the specified
4091 destination.
4090 destination.
4092
4091
4093 This operation is symmetrical to pull: it is identical to a pull
4092 This operation is symmetrical to pull: it is identical to a pull
4094 in the destination repository from the current one.
4093 in the destination repository from the current one.
4095
4094
4096 By default, push will not allow creation of new heads at the
4095 By default, push will not allow creation of new heads at the
4097 destination, since multiple heads would make it unclear which head
4096 destination, since multiple heads would make it unclear which head
4098 to use. In this situation, it is recommended to pull and merge
4097 to use. In this situation, it is recommended to pull and merge
4099 before pushing.
4098 before pushing.
4100
4099
4101 Use --new-branch if you want to allow push to create a new named
4100 Use --new-branch if you want to allow push to create a new named
4102 branch that is not present at the destination. This allows you to
4101 branch that is not present at the destination. This allows you to
4103 only create a new branch without forcing other changes.
4102 only create a new branch without forcing other changes.
4104
4103
4105 .. note::
4104 .. note::
4106
4105
4107 Extra care should be taken with the -f/--force option,
4106 Extra care should be taken with the -f/--force option,
4108 which will push all new heads on all branches, an action which will
4107 which will push all new heads on all branches, an action which will
4109 almost always cause confusion for collaborators.
4108 almost always cause confusion for collaborators.
4110
4109
4111 If -r/--rev is used, the specified revision and all its ancestors
4110 If -r/--rev is used, the specified revision and all its ancestors
4112 will be pushed to the remote repository.
4111 will be pushed to the remote repository.
4113
4112
4114 If -B/--bookmark is used, the specified bookmarked revision, its
4113 If -B/--bookmark is used, the specified bookmarked revision, its
4115 ancestors, and the bookmark will be pushed to the remote
4114 ancestors, and the bookmark will be pushed to the remote
4116 repository. Specifying ``.`` is equivalent to specifying the active
4115 repository. Specifying ``.`` is equivalent to specifying the active
4117 bookmark's name.
4116 bookmark's name.
4118
4117
4119 Please see :hg:`help urls` for important details about ``ssh://``
4118 Please see :hg:`help urls` for important details about ``ssh://``
4120 URLs. If DESTINATION is omitted, a default path will be used.
4119 URLs. If DESTINATION is omitted, a default path will be used.
4121
4120
4122 .. container:: verbose
4121 .. container:: verbose
4123
4122
4124 The --pushvars option sends strings to the server that become
4123 The --pushvars option sends strings to the server that become
4125 environment variables prepended with ``HG_USERVAR_``. For example,
4124 environment variables prepended with ``HG_USERVAR_``. For example,
4126 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4125 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4127 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4126 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4128
4127
4129 pushvars can provide for user-overridable hooks as well as set debug
4128 pushvars can provide for user-overridable hooks as well as set debug
4130 levels. One example is having a hook that blocks commits containing
4129 levels. One example is having a hook that blocks commits containing
4131 conflict markers, but enables the user to override the hook if the file
4130 conflict markers, but enables the user to override the hook if the file
4132 is using conflict markers for testing purposes or the file format has
4131 is using conflict markers for testing purposes or the file format has
4133 strings that look like conflict markers.
4132 strings that look like conflict markers.
4134
4133
4135 By default, servers will ignore `--pushvars`. To enable it add the
4134 By default, servers will ignore `--pushvars`. To enable it add the
4136 following to your configuration file::
4135 following to your configuration file::
4137
4136
4138 [push]
4137 [push]
4139 pushvars.server = true
4138 pushvars.server = true
4140
4139
4141 Returns 0 if push was successful, 1 if nothing to push.
4140 Returns 0 if push was successful, 1 if nothing to push.
4142 """
4141 """
4143
4142
4144 opts = pycompat.byteskwargs(opts)
4143 opts = pycompat.byteskwargs(opts)
4145 if opts.get('bookmark'):
4144 if opts.get('bookmark'):
4146 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4145 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4147 for b in opts['bookmark']:
4146 for b in opts['bookmark']:
4148 # translate -B options to -r so changesets get pushed
4147 # translate -B options to -r so changesets get pushed
4149 b = repo._bookmarks.expandname(b)
4148 b = repo._bookmarks.expandname(b)
4150 if b in repo._bookmarks:
4149 if b in repo._bookmarks:
4151 opts.setdefault('rev', []).append(b)
4150 opts.setdefault('rev', []).append(b)
4152 else:
4151 else:
4153 # if we try to push a deleted bookmark, translate it to null
4152 # if we try to push a deleted bookmark, translate it to null
4154 # this lets simultaneous -r, -b options continue working
4153 # this lets simultaneous -r, -b options continue working
4155 opts.setdefault('rev', []).append("null")
4154 opts.setdefault('rev', []).append("null")
4156
4155
4157 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4156 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4158 if not path:
4157 if not path:
4159 raise error.Abort(_('default repository not configured!'),
4158 raise error.Abort(_('default repository not configured!'),
4160 hint=_("see 'hg help config.paths'"))
4159 hint=_("see 'hg help config.paths'"))
4161 dest = path.pushloc or path.loc
4160 dest = path.pushloc or path.loc
4162 branches = (path.branch, opts.get('branch') or [])
4161 branches = (path.branch, opts.get('branch') or [])
4163 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4162 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4164 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4163 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4165 other = hg.peer(repo, opts, dest)
4164 other = hg.peer(repo, opts, dest)
4166
4165
4167 if revs:
4166 if revs:
4168 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4167 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4169 if not revs:
4168 if not revs:
4170 raise error.Abort(_("specified revisions evaluate to an empty set"),
4169 raise error.Abort(_("specified revisions evaluate to an empty set"),
4171 hint=_("use different revision arguments"))
4170 hint=_("use different revision arguments"))
4172 elif path.pushrev:
4171 elif path.pushrev:
4173 # It doesn't make any sense to specify ancestor revisions. So limit
4172 # It doesn't make any sense to specify ancestor revisions. So limit
4174 # to DAG heads to make discovery simpler.
4173 # to DAG heads to make discovery simpler.
4175 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4174 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4176 revs = scmutil.revrange(repo, [expr])
4175 revs = scmutil.revrange(repo, [expr])
4177 revs = [repo[rev].node() for rev in revs]
4176 revs = [repo[rev].node() for rev in revs]
4178 if not revs:
4177 if not revs:
4179 raise error.Abort(_('default push revset for path evaluates to an '
4178 raise error.Abort(_('default push revset for path evaluates to an '
4180 'empty set'))
4179 'empty set'))
4181
4180
4182 repo._subtoppath = dest
4181 repo._subtoppath = dest
4183 try:
4182 try:
4184 # push subrepos depth-first for coherent ordering
4183 # push subrepos depth-first for coherent ordering
4185 c = repo['.']
4184 c = repo['.']
4186 subs = c.substate # only repos that are committed
4185 subs = c.substate # only repos that are committed
4187 for s in sorted(subs):
4186 for s in sorted(subs):
4188 result = c.sub(s).push(opts)
4187 result = c.sub(s).push(opts)
4189 if result == 0:
4188 if result == 0:
4190 return not result
4189 return not result
4191 finally:
4190 finally:
4192 del repo._subtoppath
4191 del repo._subtoppath
4193
4192
4194 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4193 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4195 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4194 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4196
4195
4197 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4196 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4198 newbranch=opts.get('new_branch'),
4197 newbranch=opts.get('new_branch'),
4199 bookmarks=opts.get('bookmark', ()),
4198 bookmarks=opts.get('bookmark', ()),
4200 opargs=opargs)
4199 opargs=opargs)
4201
4200
4202 result = not pushop.cgresult
4201 result = not pushop.cgresult
4203
4202
4204 if pushop.bkresult is not None:
4203 if pushop.bkresult is not None:
4205 if pushop.bkresult == 2:
4204 if pushop.bkresult == 2:
4206 result = 2
4205 result = 2
4207 elif not result and pushop.bkresult:
4206 elif not result and pushop.bkresult:
4208 result = 2
4207 result = 2
4209
4208
4210 return result
4209 return result
4211
4210
4212 @command('recover', [])
4211 @command('recover', [])
4213 def recover(ui, repo):
4212 def recover(ui, repo):
4214 """roll back an interrupted transaction
4213 """roll back an interrupted transaction
4215
4214
4216 Recover from an interrupted commit or pull.
4215 Recover from an interrupted commit or pull.
4217
4216
4218 This command tries to fix the repository status after an
4217 This command tries to fix the repository status after an
4219 interrupted operation. It should only be necessary when Mercurial
4218 interrupted operation. It should only be necessary when Mercurial
4220 suggests it.
4219 suggests it.
4221
4220
4222 Returns 0 if successful, 1 if nothing to recover or verify fails.
4221 Returns 0 if successful, 1 if nothing to recover or verify fails.
4223 """
4222 """
4224 if repo.recover():
4223 if repo.recover():
4225 return hg.verify(repo)
4224 return hg.verify(repo)
4226 return 1
4225 return 1
4227
4226
4228 @command('^remove|rm',
4227 @command('^remove|rm',
4229 [('A', 'after', None, _('record delete for missing files')),
4228 [('A', 'after', None, _('record delete for missing files')),
4230 ('f', 'force', None,
4229 ('f', 'force', None,
4231 _('forget added files, delete modified files')),
4230 _('forget added files, delete modified files')),
4232 ] + subrepoopts + walkopts + dryrunopts,
4231 ] + subrepoopts + walkopts + dryrunopts,
4233 _('[OPTION]... FILE...'),
4232 _('[OPTION]... FILE...'),
4234 inferrepo=True)
4233 inferrepo=True)
4235 def remove(ui, repo, *pats, **opts):
4234 def remove(ui, repo, *pats, **opts):
4236 """remove the specified files on the next commit
4235 """remove the specified files on the next commit
4237
4236
4238 Schedule the indicated files for removal from the current branch.
4237 Schedule the indicated files for removal from the current branch.
4239
4238
4240 This command schedules the files to be removed at the next commit.
4239 This command schedules the files to be removed at the next commit.
4241 To undo a remove before that, see :hg:`revert`. To undo added
4240 To undo a remove before that, see :hg:`revert`. To undo added
4242 files, see :hg:`forget`.
4241 files, see :hg:`forget`.
4243
4242
4244 .. container:: verbose
4243 .. container:: verbose
4245
4244
4246 -A/--after can be used to remove only files that have already
4245 -A/--after can be used to remove only files that have already
4247 been deleted, -f/--force can be used to force deletion, and -Af
4246 been deleted, -f/--force can be used to force deletion, and -Af
4248 can be used to remove files from the next revision without
4247 can be used to remove files from the next revision without
4249 deleting them from the working directory.
4248 deleting them from the working directory.
4250
4249
4251 The following table details the behavior of remove for different
4250 The following table details the behavior of remove for different
4252 file states (columns) and option combinations (rows). The file
4251 file states (columns) and option combinations (rows). The file
4253 states are Added [A], Clean [C], Modified [M] and Missing [!]
4252 states are Added [A], Clean [C], Modified [M] and Missing [!]
4254 (as reported by :hg:`status`). The actions are Warn, Remove
4253 (as reported by :hg:`status`). The actions are Warn, Remove
4255 (from branch) and Delete (from disk):
4254 (from branch) and Delete (from disk):
4256
4255
4257 ========= == == == ==
4256 ========= == == == ==
4258 opt/state A C M !
4257 opt/state A C M !
4259 ========= == == == ==
4258 ========= == == == ==
4260 none W RD W R
4259 none W RD W R
4261 -f R RD RD R
4260 -f R RD RD R
4262 -A W W W R
4261 -A W W W R
4263 -Af R R R R
4262 -Af R R R R
4264 ========= == == == ==
4263 ========= == == == ==
4265
4264
4266 .. note::
4265 .. note::
4267
4266
4268 :hg:`remove` never deletes files in Added [A] state from the
4267 :hg:`remove` never deletes files in Added [A] state from the
4269 working directory, not even if ``--force`` is specified.
4268 working directory, not even if ``--force`` is specified.
4270
4269
4271 Returns 0 on success, 1 if any warnings encountered.
4270 Returns 0 on success, 1 if any warnings encountered.
4272 """
4271 """
4273
4272
4274 opts = pycompat.byteskwargs(opts)
4273 opts = pycompat.byteskwargs(opts)
4275 after, force = opts.get('after'), opts.get('force')
4274 after, force = opts.get('after'), opts.get('force')
4276 dryrun = opts.get('dry_run')
4275 dryrun = opts.get('dry_run')
4277 if not pats and not after:
4276 if not pats and not after:
4278 raise error.Abort(_('no files specified'))
4277 raise error.Abort(_('no files specified'))
4279
4278
4280 m = scmutil.match(repo[None], pats, opts)
4279 m = scmutil.match(repo[None], pats, opts)
4281 subrepos = opts.get('subrepos')
4280 subrepos = opts.get('subrepos')
4282 return cmdutil.remove(ui, repo, m, "", after, force, subrepos,
4281 return cmdutil.remove(ui, repo, m, "", after, force, subrepos,
4283 dryrun=dryrun)
4282 dryrun=dryrun)
4284
4283
4285 @command('rename|move|mv',
4284 @command('rename|move|mv',
4286 [('A', 'after', None, _('record a rename that has already occurred')),
4285 [('A', 'after', None, _('record a rename that has already occurred')),
4287 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4286 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4288 ] + walkopts + dryrunopts,
4287 ] + walkopts + dryrunopts,
4289 _('[OPTION]... SOURCE... DEST'))
4288 _('[OPTION]... SOURCE... DEST'))
4290 def rename(ui, repo, *pats, **opts):
4289 def rename(ui, repo, *pats, **opts):
4291 """rename files; equivalent of copy + remove
4290 """rename files; equivalent of copy + remove
4292
4291
4293 Mark dest as copies of sources; mark sources for deletion. If dest
4292 Mark dest as copies of sources; mark sources for deletion. If dest
4294 is a directory, copies are put in that directory. If dest is a
4293 is a directory, copies are put in that directory. If dest is a
4295 file, there can only be one source.
4294 file, there can only be one source.
4296
4295
4297 By default, this command copies the contents of files as they
4296 By default, this command copies the contents of files as they
4298 exist in the working directory. If invoked with -A/--after, the
4297 exist in the working directory. If invoked with -A/--after, the
4299 operation is recorded, but no copying is performed.
4298 operation is recorded, but no copying is performed.
4300
4299
4301 This command takes effect at the next commit. To undo a rename
4300 This command takes effect at the next commit. To undo a rename
4302 before that, see :hg:`revert`.
4301 before that, see :hg:`revert`.
4303
4302
4304 Returns 0 on success, 1 if errors are encountered.
4303 Returns 0 on success, 1 if errors are encountered.
4305 """
4304 """
4306 opts = pycompat.byteskwargs(opts)
4305 opts = pycompat.byteskwargs(opts)
4307 with repo.wlock(False):
4306 with repo.wlock(False):
4308 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4307 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4309
4308
4310 @command('resolve',
4309 @command('resolve',
4311 [('a', 'all', None, _('select all unresolved files')),
4310 [('a', 'all', None, _('select all unresolved files')),
4312 ('l', 'list', None, _('list state of files needing merge')),
4311 ('l', 'list', None, _('list state of files needing merge')),
4313 ('m', 'mark', None, _('mark files as resolved')),
4312 ('m', 'mark', None, _('mark files as resolved')),
4314 ('u', 'unmark', None, _('mark files as unresolved')),
4313 ('u', 'unmark', None, _('mark files as unresolved')),
4315 ('n', 'no-status', None, _('hide status prefix'))]
4314 ('n', 'no-status', None, _('hide status prefix'))]
4316 + mergetoolopts + walkopts + formatteropts,
4315 + mergetoolopts + walkopts + formatteropts,
4317 _('[OPTION]... [FILE]...'),
4316 _('[OPTION]... [FILE]...'),
4318 inferrepo=True)
4317 inferrepo=True)
4319 def resolve(ui, repo, *pats, **opts):
4318 def resolve(ui, repo, *pats, **opts):
4320 """redo merges or set/view the merge status of files
4319 """redo merges or set/view the merge status of files
4321
4320
4322 Merges with unresolved conflicts are often the result of
4321 Merges with unresolved conflicts are often the result of
4323 non-interactive merging using the ``internal:merge`` configuration
4322 non-interactive merging using the ``internal:merge`` configuration
4324 setting, or a command-line merge tool like ``diff3``. The resolve
4323 setting, or a command-line merge tool like ``diff3``. The resolve
4325 command is used to manage the files involved in a merge, after
4324 command is used to manage the files involved in a merge, after
4326 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4325 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4327 working directory must have two parents). See :hg:`help
4326 working directory must have two parents). See :hg:`help
4328 merge-tools` for information on configuring merge tools.
4327 merge-tools` for information on configuring merge tools.
4329
4328
4330 The resolve command can be used in the following ways:
4329 The resolve command can be used in the following ways:
4331
4330
4332 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4331 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4333 files, discarding any previous merge attempts. Re-merging is not
4332 files, discarding any previous merge attempts. Re-merging is not
4334 performed for files already marked as resolved. Use ``--all/-a``
4333 performed for files already marked as resolved. Use ``--all/-a``
4335 to select all unresolved files. ``--tool`` can be used to specify
4334 to select all unresolved files. ``--tool`` can be used to specify
4336 the merge tool used for the given files. It overrides the HGMERGE
4335 the merge tool used for the given files. It overrides the HGMERGE
4337 environment variable and your configuration files. Previous file
4336 environment variable and your configuration files. Previous file
4338 contents are saved with a ``.orig`` suffix.
4337 contents are saved with a ``.orig`` suffix.
4339
4338
4340 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4339 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4341 (e.g. after having manually fixed-up the files). The default is
4340 (e.g. after having manually fixed-up the files). The default is
4342 to mark all unresolved files.
4341 to mark all unresolved files.
4343
4342
4344 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4343 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4345 default is to mark all resolved files.
4344 default is to mark all resolved files.
4346
4345
4347 - :hg:`resolve -l`: list files which had or still have conflicts.
4346 - :hg:`resolve -l`: list files which had or still have conflicts.
4348 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4347 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4349 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4348 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4350 the list. See :hg:`help filesets` for details.
4349 the list. See :hg:`help filesets` for details.
4351
4350
4352 .. note::
4351 .. note::
4353
4352
4354 Mercurial will not let you commit files with unresolved merge
4353 Mercurial will not let you commit files with unresolved merge
4355 conflicts. You must use :hg:`resolve -m ...` before you can
4354 conflicts. You must use :hg:`resolve -m ...` before you can
4356 commit after a conflicting merge.
4355 commit after a conflicting merge.
4357
4356
4358 Returns 0 on success, 1 if any files fail a resolve attempt.
4357 Returns 0 on success, 1 if any files fail a resolve attempt.
4359 """
4358 """
4360
4359
4361 opts = pycompat.byteskwargs(opts)
4360 opts = pycompat.byteskwargs(opts)
4362 flaglist = 'all mark unmark list no_status'.split()
4361 flaglist = 'all mark unmark list no_status'.split()
4363 all, mark, unmark, show, nostatus = \
4362 all, mark, unmark, show, nostatus = \
4364 [opts.get(o) for o in flaglist]
4363 [opts.get(o) for o in flaglist]
4365
4364
4366 if (show and (mark or unmark)) or (mark and unmark):
4365 if (show and (mark or unmark)) or (mark and unmark):
4367 raise error.Abort(_("too many options specified"))
4366 raise error.Abort(_("too many options specified"))
4368 if pats and all:
4367 if pats and all:
4369 raise error.Abort(_("can't specify --all and patterns"))
4368 raise error.Abort(_("can't specify --all and patterns"))
4370 if not (all or pats or show or mark or unmark):
4369 if not (all or pats or show or mark or unmark):
4371 raise error.Abort(_('no files or directories specified'),
4370 raise error.Abort(_('no files or directories specified'),
4372 hint=('use --all to re-merge all unresolved files'))
4371 hint=('use --all to re-merge all unresolved files'))
4373
4372
4374 if show:
4373 if show:
4375 ui.pager('resolve')
4374 ui.pager('resolve')
4376 fm = ui.formatter('resolve', opts)
4375 fm = ui.formatter('resolve', opts)
4377 ms = mergemod.mergestate.read(repo)
4376 ms = mergemod.mergestate.read(repo)
4378 m = scmutil.match(repo[None], pats, opts)
4377 m = scmutil.match(repo[None], pats, opts)
4379
4378
4380 # Labels and keys based on merge state. Unresolved path conflicts show
4379 # Labels and keys based on merge state. Unresolved path conflicts show
4381 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4380 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4382 # resolved conflicts.
4381 # resolved conflicts.
4383 mergestateinfo = {
4382 mergestateinfo = {
4384 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4383 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4385 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4384 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4386 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4385 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4387 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4386 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4388 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4387 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4389 'D'),
4388 'D'),
4390 }
4389 }
4391
4390
4392 for f in ms:
4391 for f in ms:
4393 if not m(f):
4392 if not m(f):
4394 continue
4393 continue
4395
4394
4396 label, key = mergestateinfo[ms[f]]
4395 label, key = mergestateinfo[ms[f]]
4397 fm.startitem()
4396 fm.startitem()
4398 fm.condwrite(not nostatus, 'status', '%s ', key, label=label)
4397 fm.condwrite(not nostatus, 'status', '%s ', key, label=label)
4399 fm.write('path', '%s\n', f, label=label)
4398 fm.write('path', '%s\n', f, label=label)
4400 fm.end()
4399 fm.end()
4401 return 0
4400 return 0
4402
4401
4403 with repo.wlock():
4402 with repo.wlock():
4404 ms = mergemod.mergestate.read(repo)
4403 ms = mergemod.mergestate.read(repo)
4405
4404
4406 if not (ms.active() or repo.dirstate.p2() != nullid):
4405 if not (ms.active() or repo.dirstate.p2() != nullid):
4407 raise error.Abort(
4406 raise error.Abort(
4408 _('resolve command not applicable when not merging'))
4407 _('resolve command not applicable when not merging'))
4409
4408
4410 wctx = repo[None]
4409 wctx = repo[None]
4411
4410
4412 if (ms.mergedriver
4411 if (ms.mergedriver
4413 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4412 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4414 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4413 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4415 ms.commit()
4414 ms.commit()
4416 # allow mark and unmark to go through
4415 # allow mark and unmark to go through
4417 if not mark and not unmark and not proceed:
4416 if not mark and not unmark and not proceed:
4418 return 1
4417 return 1
4419
4418
4420 m = scmutil.match(wctx, pats, opts)
4419 m = scmutil.match(wctx, pats, opts)
4421 ret = 0
4420 ret = 0
4422 didwork = False
4421 didwork = False
4423 runconclude = False
4422 runconclude = False
4424
4423
4425 tocomplete = []
4424 tocomplete = []
4426 for f in ms:
4425 for f in ms:
4427 if not m(f):
4426 if not m(f):
4428 continue
4427 continue
4429
4428
4430 didwork = True
4429 didwork = True
4431
4430
4432 # don't let driver-resolved files be marked, and run the conclude
4431 # don't let driver-resolved files be marked, and run the conclude
4433 # step if asked to resolve
4432 # step if asked to resolve
4434 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4433 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4435 exact = m.exact(f)
4434 exact = m.exact(f)
4436 if mark:
4435 if mark:
4437 if exact:
4436 if exact:
4438 ui.warn(_('not marking %s as it is driver-resolved\n')
4437 ui.warn(_('not marking %s as it is driver-resolved\n')
4439 % f)
4438 % f)
4440 elif unmark:
4439 elif unmark:
4441 if exact:
4440 if exact:
4442 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4441 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4443 % f)
4442 % f)
4444 else:
4443 else:
4445 runconclude = True
4444 runconclude = True
4446 continue
4445 continue
4447
4446
4448 # path conflicts must be resolved manually
4447 # path conflicts must be resolved manually
4449 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4448 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4450 mergemod.MERGE_RECORD_RESOLVED_PATH):
4449 mergemod.MERGE_RECORD_RESOLVED_PATH):
4451 if mark:
4450 if mark:
4452 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4451 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4453 elif unmark:
4452 elif unmark:
4454 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4453 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4455 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4454 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4456 ui.warn(_('%s: path conflict must be resolved manually\n')
4455 ui.warn(_('%s: path conflict must be resolved manually\n')
4457 % f)
4456 % f)
4458 continue
4457 continue
4459
4458
4460 if mark:
4459 if mark:
4461 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4460 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4462 elif unmark:
4461 elif unmark:
4463 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4462 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4464 else:
4463 else:
4465 # backup pre-resolve (merge uses .orig for its own purposes)
4464 # backup pre-resolve (merge uses .orig for its own purposes)
4466 a = repo.wjoin(f)
4465 a = repo.wjoin(f)
4467 try:
4466 try:
4468 util.copyfile(a, a + ".resolve")
4467 util.copyfile(a, a + ".resolve")
4469 except (IOError, OSError) as inst:
4468 except (IOError, OSError) as inst:
4470 if inst.errno != errno.ENOENT:
4469 if inst.errno != errno.ENOENT:
4471 raise
4470 raise
4472
4471
4473 try:
4472 try:
4474 # preresolve file
4473 # preresolve file
4475 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4474 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4476 'resolve')
4475 'resolve')
4477 complete, r = ms.preresolve(f, wctx)
4476 complete, r = ms.preresolve(f, wctx)
4478 if not complete:
4477 if not complete:
4479 tocomplete.append(f)
4478 tocomplete.append(f)
4480 elif r:
4479 elif r:
4481 ret = 1
4480 ret = 1
4482 finally:
4481 finally:
4483 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4482 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4484 ms.commit()
4483 ms.commit()
4485
4484
4486 # replace filemerge's .orig file with our resolve file, but only
4485 # replace filemerge's .orig file with our resolve file, but only
4487 # for merges that are complete
4486 # for merges that are complete
4488 if complete:
4487 if complete:
4489 try:
4488 try:
4490 util.rename(a + ".resolve",
4489 util.rename(a + ".resolve",
4491 scmutil.origpath(ui, repo, a))
4490 scmutil.origpath(ui, repo, a))
4492 except OSError as inst:
4491 except OSError as inst:
4493 if inst.errno != errno.ENOENT:
4492 if inst.errno != errno.ENOENT:
4494 raise
4493 raise
4495
4494
4496 for f in tocomplete:
4495 for f in tocomplete:
4497 try:
4496 try:
4498 # resolve file
4497 # resolve file
4499 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4498 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4500 'resolve')
4499 'resolve')
4501 r = ms.resolve(f, wctx)
4500 r = ms.resolve(f, wctx)
4502 if r:
4501 if r:
4503 ret = 1
4502 ret = 1
4504 finally:
4503 finally:
4505 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4504 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4506 ms.commit()
4505 ms.commit()
4507
4506
4508 # replace filemerge's .orig file with our resolve file
4507 # replace filemerge's .orig file with our resolve file
4509 a = repo.wjoin(f)
4508 a = repo.wjoin(f)
4510 try:
4509 try:
4511 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4510 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4512 except OSError as inst:
4511 except OSError as inst:
4513 if inst.errno != errno.ENOENT:
4512 if inst.errno != errno.ENOENT:
4514 raise
4513 raise
4515
4514
4516 ms.commit()
4515 ms.commit()
4517 ms.recordactions()
4516 ms.recordactions()
4518
4517
4519 if not didwork and pats:
4518 if not didwork and pats:
4520 hint = None
4519 hint = None
4521 if not any([p for p in pats if p.find(':') >= 0]):
4520 if not any([p for p in pats if p.find(':') >= 0]):
4522 pats = ['path:%s' % p for p in pats]
4521 pats = ['path:%s' % p for p in pats]
4523 m = scmutil.match(wctx, pats, opts)
4522 m = scmutil.match(wctx, pats, opts)
4524 for f in ms:
4523 for f in ms:
4525 if not m(f):
4524 if not m(f):
4526 continue
4525 continue
4527 flags = ''.join(['-%s ' % o[0:1] for o in flaglist
4526 flags = ''.join(['-%s ' % o[0:1] for o in flaglist
4528 if opts.get(o)])
4527 if opts.get(o)])
4529 hint = _("(try: hg resolve %s%s)\n") % (
4528 hint = _("(try: hg resolve %s%s)\n") % (
4530 flags,
4529 flags,
4531 ' '.join(pats))
4530 ' '.join(pats))
4532 break
4531 break
4533 ui.warn(_("arguments do not match paths that need resolving\n"))
4532 ui.warn(_("arguments do not match paths that need resolving\n"))
4534 if hint:
4533 if hint:
4535 ui.warn(hint)
4534 ui.warn(hint)
4536 elif ms.mergedriver and ms.mdstate() != 's':
4535 elif ms.mergedriver and ms.mdstate() != 's':
4537 # run conclude step when either a driver-resolved file is requested
4536 # run conclude step when either a driver-resolved file is requested
4538 # or there are no driver-resolved files
4537 # or there are no driver-resolved files
4539 # we can't use 'ret' to determine whether any files are unresolved
4538 # we can't use 'ret' to determine whether any files are unresolved
4540 # because we might not have tried to resolve some
4539 # because we might not have tried to resolve some
4541 if ((runconclude or not list(ms.driverresolved()))
4540 if ((runconclude or not list(ms.driverresolved()))
4542 and not list(ms.unresolved())):
4541 and not list(ms.unresolved())):
4543 proceed = mergemod.driverconclude(repo, ms, wctx)
4542 proceed = mergemod.driverconclude(repo, ms, wctx)
4544 ms.commit()
4543 ms.commit()
4545 if not proceed:
4544 if not proceed:
4546 return 1
4545 return 1
4547
4546
4548 # Nudge users into finishing an unfinished operation
4547 # Nudge users into finishing an unfinished operation
4549 unresolvedf = list(ms.unresolved())
4548 unresolvedf = list(ms.unresolved())
4550 driverresolvedf = list(ms.driverresolved())
4549 driverresolvedf = list(ms.driverresolved())
4551 if not unresolvedf and not driverresolvedf:
4550 if not unresolvedf and not driverresolvedf:
4552 ui.status(_('(no more unresolved files)\n'))
4551 ui.status(_('(no more unresolved files)\n'))
4553 cmdutil.checkafterresolved(repo)
4552 cmdutil.checkafterresolved(repo)
4554 elif not unresolvedf:
4553 elif not unresolvedf:
4555 ui.status(_('(no more unresolved files -- '
4554 ui.status(_('(no more unresolved files -- '
4556 'run "hg resolve --all" to conclude)\n'))
4555 'run "hg resolve --all" to conclude)\n'))
4557
4556
4558 return ret
4557 return ret
4559
4558
4560 @command('revert',
4559 @command('revert',
4561 [('a', 'all', None, _('revert all changes when no arguments given')),
4560 [('a', 'all', None, _('revert all changes when no arguments given')),
4562 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4561 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4563 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4562 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4564 ('C', 'no-backup', None, _('do not save backup copies of files')),
4563 ('C', 'no-backup', None, _('do not save backup copies of files')),
4565 ('i', 'interactive', None, _('interactively select the changes')),
4564 ('i', 'interactive', None, _('interactively select the changes')),
4566 ] + walkopts + dryrunopts,
4565 ] + walkopts + dryrunopts,
4567 _('[OPTION]... [-r REV] [NAME]...'))
4566 _('[OPTION]... [-r REV] [NAME]...'))
4568 def revert(ui, repo, *pats, **opts):
4567 def revert(ui, repo, *pats, **opts):
4569 """restore files to their checkout state
4568 """restore files to their checkout state
4570
4569
4571 .. note::
4570 .. note::
4572
4571
4573 To check out earlier revisions, you should use :hg:`update REV`.
4572 To check out earlier revisions, you should use :hg:`update REV`.
4574 To cancel an uncommitted merge (and lose your changes),
4573 To cancel an uncommitted merge (and lose your changes),
4575 use :hg:`merge --abort`.
4574 use :hg:`merge --abort`.
4576
4575
4577 With no revision specified, revert the specified files or directories
4576 With no revision specified, revert the specified files or directories
4578 to the contents they had in the parent of the working directory.
4577 to the contents they had in the parent of the working directory.
4579 This restores the contents of files to an unmodified
4578 This restores the contents of files to an unmodified
4580 state and unschedules adds, removes, copies, and renames. If the
4579 state and unschedules adds, removes, copies, and renames. If the
4581 working directory has two parents, you must explicitly specify a
4580 working directory has two parents, you must explicitly specify a
4582 revision.
4581 revision.
4583
4582
4584 Using the -r/--rev or -d/--date options, revert the given files or
4583 Using the -r/--rev or -d/--date options, revert the given files or
4585 directories to their states as of a specific revision. Because
4584 directories to their states as of a specific revision. Because
4586 revert does not change the working directory parents, this will
4585 revert does not change the working directory parents, this will
4587 cause these files to appear modified. This can be helpful to "back
4586 cause these files to appear modified. This can be helpful to "back
4588 out" some or all of an earlier change. See :hg:`backout` for a
4587 out" some or all of an earlier change. See :hg:`backout` for a
4589 related method.
4588 related method.
4590
4589
4591 Modified files are saved with a .orig suffix before reverting.
4590 Modified files are saved with a .orig suffix before reverting.
4592 To disable these backups, use --no-backup. It is possible to store
4591 To disable these backups, use --no-backup. It is possible to store
4593 the backup files in a custom directory relative to the root of the
4592 the backup files in a custom directory relative to the root of the
4594 repository by setting the ``ui.origbackuppath`` configuration
4593 repository by setting the ``ui.origbackuppath`` configuration
4595 option.
4594 option.
4596
4595
4597 See :hg:`help dates` for a list of formats valid for -d/--date.
4596 See :hg:`help dates` for a list of formats valid for -d/--date.
4598
4597
4599 See :hg:`help backout` for a way to reverse the effect of an
4598 See :hg:`help backout` for a way to reverse the effect of an
4600 earlier changeset.
4599 earlier changeset.
4601
4600
4602 Returns 0 on success.
4601 Returns 0 on success.
4603 """
4602 """
4604
4603
4605 opts = pycompat.byteskwargs(opts)
4604 opts = pycompat.byteskwargs(opts)
4606 if opts.get("date"):
4605 if opts.get("date"):
4607 if opts.get("rev"):
4606 if opts.get("rev"):
4608 raise error.Abort(_("you can't specify a revision and a date"))
4607 raise error.Abort(_("you can't specify a revision and a date"))
4609 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4608 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4610
4609
4611 parent, p2 = repo.dirstate.parents()
4610 parent, p2 = repo.dirstate.parents()
4612 if not opts.get('rev') and p2 != nullid:
4611 if not opts.get('rev') and p2 != nullid:
4613 # revert after merge is a trap for new users (issue2915)
4612 # revert after merge is a trap for new users (issue2915)
4614 raise error.Abort(_('uncommitted merge with no revision specified'),
4613 raise error.Abort(_('uncommitted merge with no revision specified'),
4615 hint=_("use 'hg update' or see 'hg help revert'"))
4614 hint=_("use 'hg update' or see 'hg help revert'"))
4616
4615
4617 rev = opts.get('rev')
4616 rev = opts.get('rev')
4618 if rev:
4617 if rev:
4619 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4618 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4620 ctx = scmutil.revsingle(repo, rev)
4619 ctx = scmutil.revsingle(repo, rev)
4621
4620
4622 if (not (pats or opts.get('include') or opts.get('exclude') or
4621 if (not (pats or opts.get('include') or opts.get('exclude') or
4623 opts.get('all') or opts.get('interactive'))):
4622 opts.get('all') or opts.get('interactive'))):
4624 msg = _("no files or directories specified")
4623 msg = _("no files or directories specified")
4625 if p2 != nullid:
4624 if p2 != nullid:
4626 hint = _("uncommitted merge, use --all to discard all changes,"
4625 hint = _("uncommitted merge, use --all to discard all changes,"
4627 " or 'hg update -C .' to abort the merge")
4626 " or 'hg update -C .' to abort the merge")
4628 raise error.Abort(msg, hint=hint)
4627 raise error.Abort(msg, hint=hint)
4629 dirty = any(repo.status())
4628 dirty = any(repo.status())
4630 node = ctx.node()
4629 node = ctx.node()
4631 if node != parent:
4630 if node != parent:
4632 if dirty:
4631 if dirty:
4633 hint = _("uncommitted changes, use --all to discard all"
4632 hint = _("uncommitted changes, use --all to discard all"
4634 " changes, or 'hg update %s' to update") % ctx.rev()
4633 " changes, or 'hg update %s' to update") % ctx.rev()
4635 else:
4634 else:
4636 hint = _("use --all to revert all files,"
4635 hint = _("use --all to revert all files,"
4637 " or 'hg update %s' to update") % ctx.rev()
4636 " or 'hg update %s' to update") % ctx.rev()
4638 elif dirty:
4637 elif dirty:
4639 hint = _("uncommitted changes, use --all to discard all changes")
4638 hint = _("uncommitted changes, use --all to discard all changes")
4640 else:
4639 else:
4641 hint = _("use --all to revert all files")
4640 hint = _("use --all to revert all files")
4642 raise error.Abort(msg, hint=hint)
4641 raise error.Abort(msg, hint=hint)
4643
4642
4644 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
4643 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
4645 **pycompat.strkwargs(opts))
4644 **pycompat.strkwargs(opts))
4646
4645
4647 @command('rollback', dryrunopts +
4646 @command('rollback', dryrunopts +
4648 [('f', 'force', False, _('ignore safety measures'))])
4647 [('f', 'force', False, _('ignore safety measures'))])
4649 def rollback(ui, repo, **opts):
4648 def rollback(ui, repo, **opts):
4650 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4649 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4651
4650
4652 Please use :hg:`commit --amend` instead of rollback to correct
4651 Please use :hg:`commit --amend` instead of rollback to correct
4653 mistakes in the last commit.
4652 mistakes in the last commit.
4654
4653
4655 This command should be used with care. There is only one level of
4654 This command should be used with care. There is only one level of
4656 rollback, and there is no way to undo a rollback. It will also
4655 rollback, and there is no way to undo a rollback. It will also
4657 restore the dirstate at the time of the last transaction, losing
4656 restore the dirstate at the time of the last transaction, losing
4658 any dirstate changes since that time. This command does not alter
4657 any dirstate changes since that time. This command does not alter
4659 the working directory.
4658 the working directory.
4660
4659
4661 Transactions are used to encapsulate the effects of all commands
4660 Transactions are used to encapsulate the effects of all commands
4662 that create new changesets or propagate existing changesets into a
4661 that create new changesets or propagate existing changesets into a
4663 repository.
4662 repository.
4664
4663
4665 .. container:: verbose
4664 .. container:: verbose
4666
4665
4667 For example, the following commands are transactional, and their
4666 For example, the following commands are transactional, and their
4668 effects can be rolled back:
4667 effects can be rolled back:
4669
4668
4670 - commit
4669 - commit
4671 - import
4670 - import
4672 - pull
4671 - pull
4673 - push (with this repository as the destination)
4672 - push (with this repository as the destination)
4674 - unbundle
4673 - unbundle
4675
4674
4676 To avoid permanent data loss, rollback will refuse to rollback a
4675 To avoid permanent data loss, rollback will refuse to rollback a
4677 commit transaction if it isn't checked out. Use --force to
4676 commit transaction if it isn't checked out. Use --force to
4678 override this protection.
4677 override this protection.
4679
4678
4680 The rollback command can be entirely disabled by setting the
4679 The rollback command can be entirely disabled by setting the
4681 ``ui.rollback`` configuration setting to false. If you're here
4680 ``ui.rollback`` configuration setting to false. If you're here
4682 because you want to use rollback and it's disabled, you can
4681 because you want to use rollback and it's disabled, you can
4683 re-enable the command by setting ``ui.rollback`` to true.
4682 re-enable the command by setting ``ui.rollback`` to true.
4684
4683
4685 This command is not intended for use on public repositories. Once
4684 This command is not intended for use on public repositories. Once
4686 changes are visible for pull by other users, rolling a transaction
4685 changes are visible for pull by other users, rolling a transaction
4687 back locally is ineffective (someone else may already have pulled
4686 back locally is ineffective (someone else may already have pulled
4688 the changes). Furthermore, a race is possible with readers of the
4687 the changes). Furthermore, a race is possible with readers of the
4689 repository; for example an in-progress pull from the repository
4688 repository; for example an in-progress pull from the repository
4690 may fail if a rollback is performed.
4689 may fail if a rollback is performed.
4691
4690
4692 Returns 0 on success, 1 if no rollback data is available.
4691 Returns 0 on success, 1 if no rollback data is available.
4693 """
4692 """
4694 if not ui.configbool('ui', 'rollback'):
4693 if not ui.configbool('ui', 'rollback'):
4695 raise error.Abort(_('rollback is disabled because it is unsafe'),
4694 raise error.Abort(_('rollback is disabled because it is unsafe'),
4696 hint=('see `hg help -v rollback` for information'))
4695 hint=('see `hg help -v rollback` for information'))
4697 return repo.rollback(dryrun=opts.get(r'dry_run'),
4696 return repo.rollback(dryrun=opts.get(r'dry_run'),
4698 force=opts.get(r'force'))
4697 force=opts.get(r'force'))
4699
4698
4700 @command('root', [], cmdtype=readonly)
4699 @command('root', [], cmdtype=readonly)
4701 def root(ui, repo):
4700 def root(ui, repo):
4702 """print the root (top) of the current working directory
4701 """print the root (top) of the current working directory
4703
4702
4704 Print the root directory of the current repository.
4703 Print the root directory of the current repository.
4705
4704
4706 Returns 0 on success.
4705 Returns 0 on success.
4707 """
4706 """
4708 ui.write(repo.root + "\n")
4707 ui.write(repo.root + "\n")
4709
4708
4710 @command('^serve',
4709 @command('^serve',
4711 [('A', 'accesslog', '', _('name of access log file to write to'),
4710 [('A', 'accesslog', '', _('name of access log file to write to'),
4712 _('FILE')),
4711 _('FILE')),
4713 ('d', 'daemon', None, _('run server in background')),
4712 ('d', 'daemon', None, _('run server in background')),
4714 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4713 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4715 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4714 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4716 # use string type, then we can check if something was passed
4715 # use string type, then we can check if something was passed
4717 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4716 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4718 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4717 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4719 _('ADDR')),
4718 _('ADDR')),
4720 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4719 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4721 _('PREFIX')),
4720 _('PREFIX')),
4722 ('n', 'name', '',
4721 ('n', 'name', '',
4723 _('name to show in web pages (default: working directory)'), _('NAME')),
4722 _('name to show in web pages (default: working directory)'), _('NAME')),
4724 ('', 'web-conf', '',
4723 ('', 'web-conf', '',
4725 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4724 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4726 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4725 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4727 _('FILE')),
4726 _('FILE')),
4728 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4727 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4729 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4728 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4730 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4729 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4731 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4730 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4732 ('', 'style', '', _('template style to use'), _('STYLE')),
4731 ('', 'style', '', _('template style to use'), _('STYLE')),
4733 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4732 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4734 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))]
4733 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))]
4735 + subrepoopts,
4734 + subrepoopts,
4736 _('[OPTION]...'),
4735 _('[OPTION]...'),
4737 optionalrepo=True)
4736 optionalrepo=True)
4738 def serve(ui, repo, **opts):
4737 def serve(ui, repo, **opts):
4739 """start stand-alone webserver
4738 """start stand-alone webserver
4740
4739
4741 Start a local HTTP repository browser and pull server. You can use
4740 Start a local HTTP repository browser and pull server. You can use
4742 this for ad-hoc sharing and browsing of repositories. It is
4741 this for ad-hoc sharing and browsing of repositories. It is
4743 recommended to use a real web server to serve a repository for
4742 recommended to use a real web server to serve a repository for
4744 longer periods of time.
4743 longer periods of time.
4745
4744
4746 Please note that the server does not implement access control.
4745 Please note that the server does not implement access control.
4747 This means that, by default, anybody can read from the server and
4746 This means that, by default, anybody can read from the server and
4748 nobody can write to it by default. Set the ``web.allow-push``
4747 nobody can write to it by default. Set the ``web.allow-push``
4749 option to ``*`` to allow everybody to push to the server. You
4748 option to ``*`` to allow everybody to push to the server. You
4750 should use a real web server if you need to authenticate users.
4749 should use a real web server if you need to authenticate users.
4751
4750
4752 By default, the server logs accesses to stdout and errors to
4751 By default, the server logs accesses to stdout and errors to
4753 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4752 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4754 files.
4753 files.
4755
4754
4756 To have the server choose a free port number to listen on, specify
4755 To have the server choose a free port number to listen on, specify
4757 a port number of 0; in this case, the server will print the port
4756 a port number of 0; in this case, the server will print the port
4758 number it uses.
4757 number it uses.
4759
4758
4760 Returns 0 on success.
4759 Returns 0 on success.
4761 """
4760 """
4762
4761
4763 opts = pycompat.byteskwargs(opts)
4762 opts = pycompat.byteskwargs(opts)
4764 if opts["stdio"] and opts["cmdserver"]:
4763 if opts["stdio"] and opts["cmdserver"]:
4765 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4764 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4766
4765
4767 if opts["stdio"]:
4766 if opts["stdio"]:
4768 if repo is None:
4767 if repo is None:
4769 raise error.RepoError(_("there is no Mercurial repository here"
4768 raise error.RepoError(_("there is no Mercurial repository here"
4770 " (.hg not found)"))
4769 " (.hg not found)"))
4771 s = wireprotoserver.sshserver(ui, repo)
4770 s = wireprotoserver.sshserver(ui, repo)
4772 s.serve_forever()
4771 s.serve_forever()
4773
4772
4774 service = server.createservice(ui, repo, opts)
4773 service = server.createservice(ui, repo, opts)
4775 return server.runservice(opts, initfn=service.init, runfn=service.run)
4774 return server.runservice(opts, initfn=service.init, runfn=service.run)
4776
4775
4777 @command('^status|st',
4776 @command('^status|st',
4778 [('A', 'all', None, _('show status of all files')),
4777 [('A', 'all', None, _('show status of all files')),
4779 ('m', 'modified', None, _('show only modified files')),
4778 ('m', 'modified', None, _('show only modified files')),
4780 ('a', 'added', None, _('show only added files')),
4779 ('a', 'added', None, _('show only added files')),
4781 ('r', 'removed', None, _('show only removed files')),
4780 ('r', 'removed', None, _('show only removed files')),
4782 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4781 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4783 ('c', 'clean', None, _('show only files without changes')),
4782 ('c', 'clean', None, _('show only files without changes')),
4784 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4783 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4785 ('i', 'ignored', None, _('show only ignored files')),
4784 ('i', 'ignored', None, _('show only ignored files')),
4786 ('n', 'no-status', None, _('hide status prefix')),
4785 ('n', 'no-status', None, _('hide status prefix')),
4787 ('t', 'terse', '', _('show the terse output (EXPERIMENTAL)')),
4786 ('t', 'terse', '', _('show the terse output (EXPERIMENTAL)')),
4788 ('C', 'copies', None, _('show source of copied files')),
4787 ('C', 'copies', None, _('show source of copied files')),
4789 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4788 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4790 ('', 'rev', [], _('show difference from revision'), _('REV')),
4789 ('', 'rev', [], _('show difference from revision'), _('REV')),
4791 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4790 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4792 ] + walkopts + subrepoopts + formatteropts,
4791 ] + walkopts + subrepoopts + formatteropts,
4793 _('[OPTION]... [FILE]...'),
4792 _('[OPTION]... [FILE]...'),
4794 inferrepo=True, cmdtype=readonly)
4793 inferrepo=True, cmdtype=readonly)
4795 def status(ui, repo, *pats, **opts):
4794 def status(ui, repo, *pats, **opts):
4796 """show changed files in the working directory
4795 """show changed files in the working directory
4797
4796
4798 Show status of files in the repository. If names are given, only
4797 Show status of files in the repository. If names are given, only
4799 files that match are shown. Files that are clean or ignored or
4798 files that match are shown. Files that are clean or ignored or
4800 the source of a copy/move operation, are not listed unless
4799 the source of a copy/move operation, are not listed unless
4801 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4800 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4802 Unless options described with "show only ..." are given, the
4801 Unless options described with "show only ..." are given, the
4803 options -mardu are used.
4802 options -mardu are used.
4804
4803
4805 Option -q/--quiet hides untracked (unknown and ignored) files
4804 Option -q/--quiet hides untracked (unknown and ignored) files
4806 unless explicitly requested with -u/--unknown or -i/--ignored.
4805 unless explicitly requested with -u/--unknown or -i/--ignored.
4807
4806
4808 .. note::
4807 .. note::
4809
4808
4810 :hg:`status` may appear to disagree with diff if permissions have
4809 :hg:`status` may appear to disagree with diff if permissions have
4811 changed or a merge has occurred. The standard diff format does
4810 changed or a merge has occurred. The standard diff format does
4812 not report permission changes and diff only reports changes
4811 not report permission changes and diff only reports changes
4813 relative to one merge parent.
4812 relative to one merge parent.
4814
4813
4815 If one revision is given, it is used as the base revision.
4814 If one revision is given, it is used as the base revision.
4816 If two revisions are given, the differences between them are
4815 If two revisions are given, the differences between them are
4817 shown. The --change option can also be used as a shortcut to list
4816 shown. The --change option can also be used as a shortcut to list
4818 the changed files of a revision from its first parent.
4817 the changed files of a revision from its first parent.
4819
4818
4820 The codes used to show the status of files are::
4819 The codes used to show the status of files are::
4821
4820
4822 M = modified
4821 M = modified
4823 A = added
4822 A = added
4824 R = removed
4823 R = removed
4825 C = clean
4824 C = clean
4826 ! = missing (deleted by non-hg command, but still tracked)
4825 ! = missing (deleted by non-hg command, but still tracked)
4827 ? = not tracked
4826 ? = not tracked
4828 I = ignored
4827 I = ignored
4829 = origin of the previous file (with --copies)
4828 = origin of the previous file (with --copies)
4830
4829
4831 .. container:: verbose
4830 .. container:: verbose
4832
4831
4833 The -t/--terse option abbreviates the output by showing only the directory
4832 The -t/--terse option abbreviates the output by showing only the directory
4834 name if all the files in it share the same status. The option takes an
4833 name if all the files in it share the same status. The option takes an
4835 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
4834 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
4836 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
4835 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
4837 for 'ignored' and 'c' for clean.
4836 for 'ignored' and 'c' for clean.
4838
4837
4839 It abbreviates only those statuses which are passed. Note that clean and
4838 It abbreviates only those statuses which are passed. Note that clean and
4840 ignored files are not displayed with '--terse ic' unless the -c/--clean
4839 ignored files are not displayed with '--terse ic' unless the -c/--clean
4841 and -i/--ignored options are also used.
4840 and -i/--ignored options are also used.
4842
4841
4843 The -v/--verbose option shows information when the repository is in an
4842 The -v/--verbose option shows information when the repository is in an
4844 unfinished merge, shelve, rebase state etc. You can have this behavior
4843 unfinished merge, shelve, rebase state etc. You can have this behavior
4845 turned on by default by enabling the ``commands.status.verbose`` option.
4844 turned on by default by enabling the ``commands.status.verbose`` option.
4846
4845
4847 You can skip displaying some of these states by setting
4846 You can skip displaying some of these states by setting
4848 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
4847 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
4849 'histedit', 'merge', 'rebase', or 'unshelve'.
4848 'histedit', 'merge', 'rebase', or 'unshelve'.
4850
4849
4851 Examples:
4850 Examples:
4852
4851
4853 - show changes in the working directory relative to a
4852 - show changes in the working directory relative to a
4854 changeset::
4853 changeset::
4855
4854
4856 hg status --rev 9353
4855 hg status --rev 9353
4857
4856
4858 - show changes in the working directory relative to the
4857 - show changes in the working directory relative to the
4859 current directory (see :hg:`help patterns` for more information)::
4858 current directory (see :hg:`help patterns` for more information)::
4860
4859
4861 hg status re:
4860 hg status re:
4862
4861
4863 - show all changes including copies in an existing changeset::
4862 - show all changes including copies in an existing changeset::
4864
4863
4865 hg status --copies --change 9353
4864 hg status --copies --change 9353
4866
4865
4867 - get a NUL separated list of added files, suitable for xargs::
4866 - get a NUL separated list of added files, suitable for xargs::
4868
4867
4869 hg status -an0
4868 hg status -an0
4870
4869
4871 - show more information about the repository status, abbreviating
4870 - show more information about the repository status, abbreviating
4872 added, removed, modified, deleted, and untracked paths::
4871 added, removed, modified, deleted, and untracked paths::
4873
4872
4874 hg status -v -t mardu
4873 hg status -v -t mardu
4875
4874
4876 Returns 0 on success.
4875 Returns 0 on success.
4877
4876
4878 """
4877 """
4879
4878
4880 opts = pycompat.byteskwargs(opts)
4879 opts = pycompat.byteskwargs(opts)
4881 revs = opts.get('rev')
4880 revs = opts.get('rev')
4882 change = opts.get('change')
4881 change = opts.get('change')
4883 terse = opts.get('terse')
4882 terse = opts.get('terse')
4884
4883
4885 if revs and change:
4884 if revs and change:
4886 msg = _('cannot specify --rev and --change at the same time')
4885 msg = _('cannot specify --rev and --change at the same time')
4887 raise error.Abort(msg)
4886 raise error.Abort(msg)
4888 elif revs and terse:
4887 elif revs and terse:
4889 msg = _('cannot use --terse with --rev')
4888 msg = _('cannot use --terse with --rev')
4890 raise error.Abort(msg)
4889 raise error.Abort(msg)
4891 elif change:
4890 elif change:
4892 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
4891 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
4893 ctx2 = scmutil.revsingle(repo, change, None)
4892 ctx2 = scmutil.revsingle(repo, change, None)
4894 ctx1 = ctx2.p1()
4893 ctx1 = ctx2.p1()
4895 else:
4894 else:
4896 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
4895 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
4897 ctx1, ctx2 = scmutil.revpair(repo, revs)
4896 ctx1, ctx2 = scmutil.revpair(repo, revs)
4898
4897
4899 if pats or ui.configbool('commands', 'status.relative'):
4898 if pats or ui.configbool('commands', 'status.relative'):
4900 cwd = repo.getcwd()
4899 cwd = repo.getcwd()
4901 else:
4900 else:
4902 cwd = ''
4901 cwd = ''
4903
4902
4904 if opts.get('print0'):
4903 if opts.get('print0'):
4905 end = '\0'
4904 end = '\0'
4906 else:
4905 else:
4907 end = '\n'
4906 end = '\n'
4908 copy = {}
4907 copy = {}
4909 states = 'modified added removed deleted unknown ignored clean'.split()
4908 states = 'modified added removed deleted unknown ignored clean'.split()
4910 show = [k for k in states if opts.get(k)]
4909 show = [k for k in states if opts.get(k)]
4911 if opts.get('all'):
4910 if opts.get('all'):
4912 show += ui.quiet and (states[:4] + ['clean']) or states
4911 show += ui.quiet and (states[:4] + ['clean']) or states
4913
4912
4914 if not show:
4913 if not show:
4915 if ui.quiet:
4914 if ui.quiet:
4916 show = states[:4]
4915 show = states[:4]
4917 else:
4916 else:
4918 show = states[:5]
4917 show = states[:5]
4919
4918
4920 m = scmutil.match(ctx2, pats, opts)
4919 m = scmutil.match(ctx2, pats, opts)
4921 if terse:
4920 if terse:
4922 # we need to compute clean and unknown to terse
4921 # we need to compute clean and unknown to terse
4923 stat = repo.status(ctx1.node(), ctx2.node(), m,
4922 stat = repo.status(ctx1.node(), ctx2.node(), m,
4924 'ignored' in show or 'i' in terse,
4923 'ignored' in show or 'i' in terse,
4925 True, True, opts.get('subrepos'))
4924 True, True, opts.get('subrepos'))
4926
4925
4927 stat = cmdutil.tersedir(stat, terse)
4926 stat = cmdutil.tersedir(stat, terse)
4928 else:
4927 else:
4929 stat = repo.status(ctx1.node(), ctx2.node(), m,
4928 stat = repo.status(ctx1.node(), ctx2.node(), m,
4930 'ignored' in show, 'clean' in show,
4929 'ignored' in show, 'clean' in show,
4931 'unknown' in show, opts.get('subrepos'))
4930 'unknown' in show, opts.get('subrepos'))
4932
4931
4933 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
4932 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
4934
4933
4935 if (opts.get('all') or opts.get('copies')
4934 if (opts.get('all') or opts.get('copies')
4936 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
4935 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
4937 copy = copies.pathcopies(ctx1, ctx2, m)
4936 copy = copies.pathcopies(ctx1, ctx2, m)
4938
4937
4939 ui.pager('status')
4938 ui.pager('status')
4940 fm = ui.formatter('status', opts)
4939 fm = ui.formatter('status', opts)
4941 fmt = '%s' + end
4940 fmt = '%s' + end
4942 showchar = not opts.get('no_status')
4941 showchar = not opts.get('no_status')
4943
4942
4944 for state, char, files in changestates:
4943 for state, char, files in changestates:
4945 if state in show:
4944 if state in show:
4946 label = 'status.' + state
4945 label = 'status.' + state
4947 for f in files:
4946 for f in files:
4948 fm.startitem()
4947 fm.startitem()
4949 fm.condwrite(showchar, 'status', '%s ', char, label=label)
4948 fm.condwrite(showchar, 'status', '%s ', char, label=label)
4950 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
4949 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
4951 if f in copy:
4950 if f in copy:
4952 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
4951 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
4953 label='status.copied')
4952 label='status.copied')
4954
4953
4955 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
4954 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
4956 and not ui.plain()):
4955 and not ui.plain()):
4957 cmdutil.morestatus(repo, fm)
4956 cmdutil.morestatus(repo, fm)
4958 fm.end()
4957 fm.end()
4959
4958
4960 @command('^summary|sum',
4959 @command('^summary|sum',
4961 [('', 'remote', None, _('check for push and pull'))],
4960 [('', 'remote', None, _('check for push and pull'))],
4962 '[--remote]', cmdtype=readonly)
4961 '[--remote]', cmdtype=readonly)
4963 def summary(ui, repo, **opts):
4962 def summary(ui, repo, **opts):
4964 """summarize working directory state
4963 """summarize working directory state
4965
4964
4966 This generates a brief summary of the working directory state,
4965 This generates a brief summary of the working directory state,
4967 including parents, branch, commit status, phase and available updates.
4966 including parents, branch, commit status, phase and available updates.
4968
4967
4969 With the --remote option, this will check the default paths for
4968 With the --remote option, this will check the default paths for
4970 incoming and outgoing changes. This can be time-consuming.
4969 incoming and outgoing changes. This can be time-consuming.
4971
4970
4972 Returns 0 on success.
4971 Returns 0 on success.
4973 """
4972 """
4974
4973
4975 opts = pycompat.byteskwargs(opts)
4974 opts = pycompat.byteskwargs(opts)
4976 ui.pager('summary')
4975 ui.pager('summary')
4977 ctx = repo[None]
4976 ctx = repo[None]
4978 parents = ctx.parents()
4977 parents = ctx.parents()
4979 pnode = parents[0].node()
4978 pnode = parents[0].node()
4980 marks = []
4979 marks = []
4981
4980
4982 ms = None
4981 ms = None
4983 try:
4982 try:
4984 ms = mergemod.mergestate.read(repo)
4983 ms = mergemod.mergestate.read(repo)
4985 except error.UnsupportedMergeRecords as e:
4984 except error.UnsupportedMergeRecords as e:
4986 s = ' '.join(e.recordtypes)
4985 s = ' '.join(e.recordtypes)
4987 ui.warn(
4986 ui.warn(
4988 _('warning: merge state has unsupported record types: %s\n') % s)
4987 _('warning: merge state has unsupported record types: %s\n') % s)
4989 unresolved = []
4988 unresolved = []
4990 else:
4989 else:
4991 unresolved = list(ms.unresolved())
4990 unresolved = list(ms.unresolved())
4992
4991
4993 for p in parents:
4992 for p in parents:
4994 # label with log.changeset (instead of log.parent) since this
4993 # label with log.changeset (instead of log.parent) since this
4995 # shows a working directory parent *changeset*:
4994 # shows a working directory parent *changeset*:
4996 # i18n: column positioning for "hg summary"
4995 # i18n: column positioning for "hg summary"
4997 ui.write(_('parent: %d:%s ') % (p.rev(), p),
4996 ui.write(_('parent: %d:%s ') % (p.rev(), p),
4998 label=logcmdutil.changesetlabels(p))
4997 label=logcmdutil.changesetlabels(p))
4999 ui.write(' '.join(p.tags()), label='log.tag')
4998 ui.write(' '.join(p.tags()), label='log.tag')
5000 if p.bookmarks():
4999 if p.bookmarks():
5001 marks.extend(p.bookmarks())
5000 marks.extend(p.bookmarks())
5002 if p.rev() == -1:
5001 if p.rev() == -1:
5003 if not len(repo):
5002 if not len(repo):
5004 ui.write(_(' (empty repository)'))
5003 ui.write(_(' (empty repository)'))
5005 else:
5004 else:
5006 ui.write(_(' (no revision checked out)'))
5005 ui.write(_(' (no revision checked out)'))
5007 if p.obsolete():
5006 if p.obsolete():
5008 ui.write(_(' (obsolete)'))
5007 ui.write(_(' (obsolete)'))
5009 if p.isunstable():
5008 if p.isunstable():
5010 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5009 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5011 for instability in p.instabilities())
5010 for instability in p.instabilities())
5012 ui.write(' ('
5011 ui.write(' ('
5013 + ', '.join(instabilities)
5012 + ', '.join(instabilities)
5014 + ')')
5013 + ')')
5015 ui.write('\n')
5014 ui.write('\n')
5016 if p.description():
5015 if p.description():
5017 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5016 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5018 label='log.summary')
5017 label='log.summary')
5019
5018
5020 branch = ctx.branch()
5019 branch = ctx.branch()
5021 bheads = repo.branchheads(branch)
5020 bheads = repo.branchheads(branch)
5022 # i18n: column positioning for "hg summary"
5021 # i18n: column positioning for "hg summary"
5023 m = _('branch: %s\n') % branch
5022 m = _('branch: %s\n') % branch
5024 if branch != 'default':
5023 if branch != 'default':
5025 ui.write(m, label='log.branch')
5024 ui.write(m, label='log.branch')
5026 else:
5025 else:
5027 ui.status(m, label='log.branch')
5026 ui.status(m, label='log.branch')
5028
5027
5029 if marks:
5028 if marks:
5030 active = repo._activebookmark
5029 active = repo._activebookmark
5031 # i18n: column positioning for "hg summary"
5030 # i18n: column positioning for "hg summary"
5032 ui.write(_('bookmarks:'), label='log.bookmark')
5031 ui.write(_('bookmarks:'), label='log.bookmark')
5033 if active is not None:
5032 if active is not None:
5034 if active in marks:
5033 if active in marks:
5035 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5034 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5036 marks.remove(active)
5035 marks.remove(active)
5037 else:
5036 else:
5038 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5037 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5039 for m in marks:
5038 for m in marks:
5040 ui.write(' ' + m, label='log.bookmark')
5039 ui.write(' ' + m, label='log.bookmark')
5041 ui.write('\n', label='log.bookmark')
5040 ui.write('\n', label='log.bookmark')
5042
5041
5043 status = repo.status(unknown=True)
5042 status = repo.status(unknown=True)
5044
5043
5045 c = repo.dirstate.copies()
5044 c = repo.dirstate.copies()
5046 copied, renamed = [], []
5045 copied, renamed = [], []
5047 for d, s in c.iteritems():
5046 for d, s in c.iteritems():
5048 if s in status.removed:
5047 if s in status.removed:
5049 status.removed.remove(s)
5048 status.removed.remove(s)
5050 renamed.append(d)
5049 renamed.append(d)
5051 else:
5050 else:
5052 copied.append(d)
5051 copied.append(d)
5053 if d in status.added:
5052 if d in status.added:
5054 status.added.remove(d)
5053 status.added.remove(d)
5055
5054
5056 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5055 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5057
5056
5058 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5057 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5059 (ui.label(_('%d added'), 'status.added'), status.added),
5058 (ui.label(_('%d added'), 'status.added'), status.added),
5060 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5059 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5061 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5060 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5062 (ui.label(_('%d copied'), 'status.copied'), copied),
5061 (ui.label(_('%d copied'), 'status.copied'), copied),
5063 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5062 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5064 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5063 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5065 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5064 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5066 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5065 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5067 t = []
5066 t = []
5068 for l, s in labels:
5067 for l, s in labels:
5069 if s:
5068 if s:
5070 t.append(l % len(s))
5069 t.append(l % len(s))
5071
5070
5072 t = ', '.join(t)
5071 t = ', '.join(t)
5073 cleanworkdir = False
5072 cleanworkdir = False
5074
5073
5075 if repo.vfs.exists('graftstate'):
5074 if repo.vfs.exists('graftstate'):
5076 t += _(' (graft in progress)')
5075 t += _(' (graft in progress)')
5077 if repo.vfs.exists('updatestate'):
5076 if repo.vfs.exists('updatestate'):
5078 t += _(' (interrupted update)')
5077 t += _(' (interrupted update)')
5079 elif len(parents) > 1:
5078 elif len(parents) > 1:
5080 t += _(' (merge)')
5079 t += _(' (merge)')
5081 elif branch != parents[0].branch():
5080 elif branch != parents[0].branch():
5082 t += _(' (new branch)')
5081 t += _(' (new branch)')
5083 elif (parents[0].closesbranch() and
5082 elif (parents[0].closesbranch() and
5084 pnode in repo.branchheads(branch, closed=True)):
5083 pnode in repo.branchheads(branch, closed=True)):
5085 t += _(' (head closed)')
5084 t += _(' (head closed)')
5086 elif not (status.modified or status.added or status.removed or renamed or
5085 elif not (status.modified or status.added or status.removed or renamed or
5087 copied or subs):
5086 copied or subs):
5088 t += _(' (clean)')
5087 t += _(' (clean)')
5089 cleanworkdir = True
5088 cleanworkdir = True
5090 elif pnode not in bheads:
5089 elif pnode not in bheads:
5091 t += _(' (new branch head)')
5090 t += _(' (new branch head)')
5092
5091
5093 if parents:
5092 if parents:
5094 pendingphase = max(p.phase() for p in parents)
5093 pendingphase = max(p.phase() for p in parents)
5095 else:
5094 else:
5096 pendingphase = phases.public
5095 pendingphase = phases.public
5097
5096
5098 if pendingphase > phases.newcommitphase(ui):
5097 if pendingphase > phases.newcommitphase(ui):
5099 t += ' (%s)' % phases.phasenames[pendingphase]
5098 t += ' (%s)' % phases.phasenames[pendingphase]
5100
5099
5101 if cleanworkdir:
5100 if cleanworkdir:
5102 # i18n: column positioning for "hg summary"
5101 # i18n: column positioning for "hg summary"
5103 ui.status(_('commit: %s\n') % t.strip())
5102 ui.status(_('commit: %s\n') % t.strip())
5104 else:
5103 else:
5105 # i18n: column positioning for "hg summary"
5104 # i18n: column positioning for "hg summary"
5106 ui.write(_('commit: %s\n') % t.strip())
5105 ui.write(_('commit: %s\n') % t.strip())
5107
5106
5108 # all ancestors of branch heads - all ancestors of parent = new csets
5107 # all ancestors of branch heads - all ancestors of parent = new csets
5109 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5108 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5110 bheads))
5109 bheads))
5111
5110
5112 if new == 0:
5111 if new == 0:
5113 # i18n: column positioning for "hg summary"
5112 # i18n: column positioning for "hg summary"
5114 ui.status(_('update: (current)\n'))
5113 ui.status(_('update: (current)\n'))
5115 elif pnode not in bheads:
5114 elif pnode not in bheads:
5116 # i18n: column positioning for "hg summary"
5115 # i18n: column positioning for "hg summary"
5117 ui.write(_('update: %d new changesets (update)\n') % new)
5116 ui.write(_('update: %d new changesets (update)\n') % new)
5118 else:
5117 else:
5119 # i18n: column positioning for "hg summary"
5118 # i18n: column positioning for "hg summary"
5120 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5119 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5121 (new, len(bheads)))
5120 (new, len(bheads)))
5122
5121
5123 t = []
5122 t = []
5124 draft = len(repo.revs('draft()'))
5123 draft = len(repo.revs('draft()'))
5125 if draft:
5124 if draft:
5126 t.append(_('%d draft') % draft)
5125 t.append(_('%d draft') % draft)
5127 secret = len(repo.revs('secret()'))
5126 secret = len(repo.revs('secret()'))
5128 if secret:
5127 if secret:
5129 t.append(_('%d secret') % secret)
5128 t.append(_('%d secret') % secret)
5130
5129
5131 if draft or secret:
5130 if draft or secret:
5132 ui.status(_('phases: %s\n') % ', '.join(t))
5131 ui.status(_('phases: %s\n') % ', '.join(t))
5133
5132
5134 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5133 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5135 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5134 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5136 numtrouble = len(repo.revs(trouble + "()"))
5135 numtrouble = len(repo.revs(trouble + "()"))
5137 # We write all the possibilities to ease translation
5136 # We write all the possibilities to ease translation
5138 troublemsg = {
5137 troublemsg = {
5139 "orphan": _("orphan: %d changesets"),
5138 "orphan": _("orphan: %d changesets"),
5140 "contentdivergent": _("content-divergent: %d changesets"),
5139 "contentdivergent": _("content-divergent: %d changesets"),
5141 "phasedivergent": _("phase-divergent: %d changesets"),
5140 "phasedivergent": _("phase-divergent: %d changesets"),
5142 }
5141 }
5143 if numtrouble > 0:
5142 if numtrouble > 0:
5144 ui.status(troublemsg[trouble] % numtrouble + "\n")
5143 ui.status(troublemsg[trouble] % numtrouble + "\n")
5145
5144
5146 cmdutil.summaryhooks(ui, repo)
5145 cmdutil.summaryhooks(ui, repo)
5147
5146
5148 if opts.get('remote'):
5147 if opts.get('remote'):
5149 needsincoming, needsoutgoing = True, True
5148 needsincoming, needsoutgoing = True, True
5150 else:
5149 else:
5151 needsincoming, needsoutgoing = False, False
5150 needsincoming, needsoutgoing = False, False
5152 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5151 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5153 if i:
5152 if i:
5154 needsincoming = True
5153 needsincoming = True
5155 if o:
5154 if o:
5156 needsoutgoing = True
5155 needsoutgoing = True
5157 if not needsincoming and not needsoutgoing:
5156 if not needsincoming and not needsoutgoing:
5158 return
5157 return
5159
5158
5160 def getincoming():
5159 def getincoming():
5161 source, branches = hg.parseurl(ui.expandpath('default'))
5160 source, branches = hg.parseurl(ui.expandpath('default'))
5162 sbranch = branches[0]
5161 sbranch = branches[0]
5163 try:
5162 try:
5164 other = hg.peer(repo, {}, source)
5163 other = hg.peer(repo, {}, source)
5165 except error.RepoError:
5164 except error.RepoError:
5166 if opts.get('remote'):
5165 if opts.get('remote'):
5167 raise
5166 raise
5168 return source, sbranch, None, None, None
5167 return source, sbranch, None, None, None
5169 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5168 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5170 if revs:
5169 if revs:
5171 revs = [other.lookup(rev) for rev in revs]
5170 revs = [other.lookup(rev) for rev in revs]
5172 ui.debug('comparing with %s\n' % util.hidepassword(source))
5171 ui.debug('comparing with %s\n' % util.hidepassword(source))
5173 repo.ui.pushbuffer()
5172 repo.ui.pushbuffer()
5174 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5173 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5175 repo.ui.popbuffer()
5174 repo.ui.popbuffer()
5176 return source, sbranch, other, commoninc, commoninc[1]
5175 return source, sbranch, other, commoninc, commoninc[1]
5177
5176
5178 if needsincoming:
5177 if needsincoming:
5179 source, sbranch, sother, commoninc, incoming = getincoming()
5178 source, sbranch, sother, commoninc, incoming = getincoming()
5180 else:
5179 else:
5181 source = sbranch = sother = commoninc = incoming = None
5180 source = sbranch = sother = commoninc = incoming = None
5182
5181
5183 def getoutgoing():
5182 def getoutgoing():
5184 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5183 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5185 dbranch = branches[0]
5184 dbranch = branches[0]
5186 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5185 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5187 if source != dest:
5186 if source != dest:
5188 try:
5187 try:
5189 dother = hg.peer(repo, {}, dest)
5188 dother = hg.peer(repo, {}, dest)
5190 except error.RepoError:
5189 except error.RepoError:
5191 if opts.get('remote'):
5190 if opts.get('remote'):
5192 raise
5191 raise
5193 return dest, dbranch, None, None
5192 return dest, dbranch, None, None
5194 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5193 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5195 elif sother is None:
5194 elif sother is None:
5196 # there is no explicit destination peer, but source one is invalid
5195 # there is no explicit destination peer, but source one is invalid
5197 return dest, dbranch, None, None
5196 return dest, dbranch, None, None
5198 else:
5197 else:
5199 dother = sother
5198 dother = sother
5200 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5199 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5201 common = None
5200 common = None
5202 else:
5201 else:
5203 common = commoninc
5202 common = commoninc
5204 if revs:
5203 if revs:
5205 revs = [repo.lookup(rev) for rev in revs]
5204 revs = [repo.lookup(rev) for rev in revs]
5206 repo.ui.pushbuffer()
5205 repo.ui.pushbuffer()
5207 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5206 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5208 commoninc=common)
5207 commoninc=common)
5209 repo.ui.popbuffer()
5208 repo.ui.popbuffer()
5210 return dest, dbranch, dother, outgoing
5209 return dest, dbranch, dother, outgoing
5211
5210
5212 if needsoutgoing:
5211 if needsoutgoing:
5213 dest, dbranch, dother, outgoing = getoutgoing()
5212 dest, dbranch, dother, outgoing = getoutgoing()
5214 else:
5213 else:
5215 dest = dbranch = dother = outgoing = None
5214 dest = dbranch = dother = outgoing = None
5216
5215
5217 if opts.get('remote'):
5216 if opts.get('remote'):
5218 t = []
5217 t = []
5219 if incoming:
5218 if incoming:
5220 t.append(_('1 or more incoming'))
5219 t.append(_('1 or more incoming'))
5221 o = outgoing.missing
5220 o = outgoing.missing
5222 if o:
5221 if o:
5223 t.append(_('%d outgoing') % len(o))
5222 t.append(_('%d outgoing') % len(o))
5224 other = dother or sother
5223 other = dother or sother
5225 if 'bookmarks' in other.listkeys('namespaces'):
5224 if 'bookmarks' in other.listkeys('namespaces'):
5226 counts = bookmarks.summary(repo, other)
5225 counts = bookmarks.summary(repo, other)
5227 if counts[0] > 0:
5226 if counts[0] > 0:
5228 t.append(_('%d incoming bookmarks') % counts[0])
5227 t.append(_('%d incoming bookmarks') % counts[0])
5229 if counts[1] > 0:
5228 if counts[1] > 0:
5230 t.append(_('%d outgoing bookmarks') % counts[1])
5229 t.append(_('%d outgoing bookmarks') % counts[1])
5231
5230
5232 if t:
5231 if t:
5233 # i18n: column positioning for "hg summary"
5232 # i18n: column positioning for "hg summary"
5234 ui.write(_('remote: %s\n') % (', '.join(t)))
5233 ui.write(_('remote: %s\n') % (', '.join(t)))
5235 else:
5234 else:
5236 # i18n: column positioning for "hg summary"
5235 # i18n: column positioning for "hg summary"
5237 ui.status(_('remote: (synced)\n'))
5236 ui.status(_('remote: (synced)\n'))
5238
5237
5239 cmdutil.summaryremotehooks(ui, repo, opts,
5238 cmdutil.summaryremotehooks(ui, repo, opts,
5240 ((source, sbranch, sother, commoninc),
5239 ((source, sbranch, sother, commoninc),
5241 (dest, dbranch, dother, outgoing)))
5240 (dest, dbranch, dother, outgoing)))
5242
5241
5243 @command('tag',
5242 @command('tag',
5244 [('f', 'force', None, _('force tag')),
5243 [('f', 'force', None, _('force tag')),
5245 ('l', 'local', None, _('make the tag local')),
5244 ('l', 'local', None, _('make the tag local')),
5246 ('r', 'rev', '', _('revision to tag'), _('REV')),
5245 ('r', 'rev', '', _('revision to tag'), _('REV')),
5247 ('', 'remove', None, _('remove a tag')),
5246 ('', 'remove', None, _('remove a tag')),
5248 # -l/--local is already there, commitopts cannot be used
5247 # -l/--local is already there, commitopts cannot be used
5249 ('e', 'edit', None, _('invoke editor on commit messages')),
5248 ('e', 'edit', None, _('invoke editor on commit messages')),
5250 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5249 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5251 ] + commitopts2,
5250 ] + commitopts2,
5252 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5251 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5253 def tag(ui, repo, name1, *names, **opts):
5252 def tag(ui, repo, name1, *names, **opts):
5254 """add one or more tags for the current or given revision
5253 """add one or more tags for the current or given revision
5255
5254
5256 Name a particular revision using <name>.
5255 Name a particular revision using <name>.
5257
5256
5258 Tags are used to name particular revisions of the repository and are
5257 Tags are used to name particular revisions of the repository and are
5259 very useful to compare different revisions, to go back to significant
5258 very useful to compare different revisions, to go back to significant
5260 earlier versions or to mark branch points as releases, etc. Changing
5259 earlier versions or to mark branch points as releases, etc. Changing
5261 an existing tag is normally disallowed; use -f/--force to override.
5260 an existing tag is normally disallowed; use -f/--force to override.
5262
5261
5263 If no revision is given, the parent of the working directory is
5262 If no revision is given, the parent of the working directory is
5264 used.
5263 used.
5265
5264
5266 To facilitate version control, distribution, and merging of tags,
5265 To facilitate version control, distribution, and merging of tags,
5267 they are stored as a file named ".hgtags" which is managed similarly
5266 they are stored as a file named ".hgtags" which is managed similarly
5268 to other project files and can be hand-edited if necessary. This
5267 to other project files and can be hand-edited if necessary. This
5269 also means that tagging creates a new commit. The file
5268 also means that tagging creates a new commit. The file
5270 ".hg/localtags" is used for local tags (not shared among
5269 ".hg/localtags" is used for local tags (not shared among
5271 repositories).
5270 repositories).
5272
5271
5273 Tag commits are usually made at the head of a branch. If the parent
5272 Tag commits are usually made at the head of a branch. If the parent
5274 of the working directory is not a branch head, :hg:`tag` aborts; use
5273 of the working directory is not a branch head, :hg:`tag` aborts; use
5275 -f/--force to force the tag commit to be based on a non-head
5274 -f/--force to force the tag commit to be based on a non-head
5276 changeset.
5275 changeset.
5277
5276
5278 See :hg:`help dates` for a list of formats valid for -d/--date.
5277 See :hg:`help dates` for a list of formats valid for -d/--date.
5279
5278
5280 Since tag names have priority over branch names during revision
5279 Since tag names have priority over branch names during revision
5281 lookup, using an existing branch name as a tag name is discouraged.
5280 lookup, using an existing branch name as a tag name is discouraged.
5282
5281
5283 Returns 0 on success.
5282 Returns 0 on success.
5284 """
5283 """
5285 opts = pycompat.byteskwargs(opts)
5284 opts = pycompat.byteskwargs(opts)
5286 wlock = lock = None
5285 wlock = lock = None
5287 try:
5286 try:
5288 wlock = repo.wlock()
5287 wlock = repo.wlock()
5289 lock = repo.lock()
5288 lock = repo.lock()
5290 rev_ = "."
5289 rev_ = "."
5291 names = [t.strip() for t in (name1,) + names]
5290 names = [t.strip() for t in (name1,) + names]
5292 if len(names) != len(set(names)):
5291 if len(names) != len(set(names)):
5293 raise error.Abort(_('tag names must be unique'))
5292 raise error.Abort(_('tag names must be unique'))
5294 for n in names:
5293 for n in names:
5295 scmutil.checknewlabel(repo, n, 'tag')
5294 scmutil.checknewlabel(repo, n, 'tag')
5296 if not n:
5295 if not n:
5297 raise error.Abort(_('tag names cannot consist entirely of '
5296 raise error.Abort(_('tag names cannot consist entirely of '
5298 'whitespace'))
5297 'whitespace'))
5299 if opts.get('rev') and opts.get('remove'):
5298 if opts.get('rev') and opts.get('remove'):
5300 raise error.Abort(_("--rev and --remove are incompatible"))
5299 raise error.Abort(_("--rev and --remove are incompatible"))
5301 if opts.get('rev'):
5300 if opts.get('rev'):
5302 rev_ = opts['rev']
5301 rev_ = opts['rev']
5303 message = opts.get('message')
5302 message = opts.get('message')
5304 if opts.get('remove'):
5303 if opts.get('remove'):
5305 if opts.get('local'):
5304 if opts.get('local'):
5306 expectedtype = 'local'
5305 expectedtype = 'local'
5307 else:
5306 else:
5308 expectedtype = 'global'
5307 expectedtype = 'global'
5309
5308
5310 for n in names:
5309 for n in names:
5311 if not repo.tagtype(n):
5310 if not repo.tagtype(n):
5312 raise error.Abort(_("tag '%s' does not exist") % n)
5311 raise error.Abort(_("tag '%s' does not exist") % n)
5313 if repo.tagtype(n) != expectedtype:
5312 if repo.tagtype(n) != expectedtype:
5314 if expectedtype == 'global':
5313 if expectedtype == 'global':
5315 raise error.Abort(_("tag '%s' is not a global tag") % n)
5314 raise error.Abort(_("tag '%s' is not a global tag") % n)
5316 else:
5315 else:
5317 raise error.Abort(_("tag '%s' is not a local tag") % n)
5316 raise error.Abort(_("tag '%s' is not a local tag") % n)
5318 rev_ = 'null'
5317 rev_ = 'null'
5319 if not message:
5318 if not message:
5320 # we don't translate commit messages
5319 # we don't translate commit messages
5321 message = 'Removed tag %s' % ', '.join(names)
5320 message = 'Removed tag %s' % ', '.join(names)
5322 elif not opts.get('force'):
5321 elif not opts.get('force'):
5323 for n in names:
5322 for n in names:
5324 if n in repo.tags():
5323 if n in repo.tags():
5325 raise error.Abort(_("tag '%s' already exists "
5324 raise error.Abort(_("tag '%s' already exists "
5326 "(use -f to force)") % n)
5325 "(use -f to force)") % n)
5327 if not opts.get('local'):
5326 if not opts.get('local'):
5328 p1, p2 = repo.dirstate.parents()
5327 p1, p2 = repo.dirstate.parents()
5329 if p2 != nullid:
5328 if p2 != nullid:
5330 raise error.Abort(_('uncommitted merge'))
5329 raise error.Abort(_('uncommitted merge'))
5331 bheads = repo.branchheads()
5330 bheads = repo.branchheads()
5332 if not opts.get('force') and bheads and p1 not in bheads:
5331 if not opts.get('force') and bheads and p1 not in bheads:
5333 raise error.Abort(_('working directory is not at a branch head '
5332 raise error.Abort(_('working directory is not at a branch head '
5334 '(use -f to force)'))
5333 '(use -f to force)'))
5335 node = scmutil.revsingle(repo, rev_).node()
5334 node = scmutil.revsingle(repo, rev_).node()
5336
5335
5337 if not message:
5336 if not message:
5338 # we don't translate commit messages
5337 # we don't translate commit messages
5339 message = ('Added tag %s for changeset %s' %
5338 message = ('Added tag %s for changeset %s' %
5340 (', '.join(names), short(node)))
5339 (', '.join(names), short(node)))
5341
5340
5342 date = opts.get('date')
5341 date = opts.get('date')
5343 if date:
5342 if date:
5344 date = dateutil.parsedate(date)
5343 date = dateutil.parsedate(date)
5345
5344
5346 if opts.get('remove'):
5345 if opts.get('remove'):
5347 editform = 'tag.remove'
5346 editform = 'tag.remove'
5348 else:
5347 else:
5349 editform = 'tag.add'
5348 editform = 'tag.add'
5350 editor = cmdutil.getcommiteditor(editform=editform,
5349 editor = cmdutil.getcommiteditor(editform=editform,
5351 **pycompat.strkwargs(opts))
5350 **pycompat.strkwargs(opts))
5352
5351
5353 # don't allow tagging the null rev
5352 # don't allow tagging the null rev
5354 if (not opts.get('remove') and
5353 if (not opts.get('remove') and
5355 scmutil.revsingle(repo, rev_).rev() == nullrev):
5354 scmutil.revsingle(repo, rev_).rev() == nullrev):
5356 raise error.Abort(_("cannot tag null revision"))
5355 raise error.Abort(_("cannot tag null revision"))
5357
5356
5358 tagsmod.tag(repo, names, node, message, opts.get('local'),
5357 tagsmod.tag(repo, names, node, message, opts.get('local'),
5359 opts.get('user'), date, editor=editor)
5358 opts.get('user'), date, editor=editor)
5360 finally:
5359 finally:
5361 release(lock, wlock)
5360 release(lock, wlock)
5362
5361
5363 @command('tags', formatteropts, '', cmdtype=readonly)
5362 @command('tags', formatteropts, '', cmdtype=readonly)
5364 def tags(ui, repo, **opts):
5363 def tags(ui, repo, **opts):
5365 """list repository tags
5364 """list repository tags
5366
5365
5367 This lists both regular and local tags. When the -v/--verbose
5366 This lists both regular and local tags. When the -v/--verbose
5368 switch is used, a third column "local" is printed for local tags.
5367 switch is used, a third column "local" is printed for local tags.
5369 When the -q/--quiet switch is used, only the tag name is printed.
5368 When the -q/--quiet switch is used, only the tag name is printed.
5370
5369
5371 Returns 0 on success.
5370 Returns 0 on success.
5372 """
5371 """
5373
5372
5374 opts = pycompat.byteskwargs(opts)
5373 opts = pycompat.byteskwargs(opts)
5375 ui.pager('tags')
5374 ui.pager('tags')
5376 fm = ui.formatter('tags', opts)
5375 fm = ui.formatter('tags', opts)
5377 hexfunc = fm.hexfunc
5376 hexfunc = fm.hexfunc
5378 tagtype = ""
5377 tagtype = ""
5379
5378
5380 for t, n in reversed(repo.tagslist()):
5379 for t, n in reversed(repo.tagslist()):
5381 hn = hexfunc(n)
5380 hn = hexfunc(n)
5382 label = 'tags.normal'
5381 label = 'tags.normal'
5383 tagtype = ''
5382 tagtype = ''
5384 if repo.tagtype(t) == 'local':
5383 if repo.tagtype(t) == 'local':
5385 label = 'tags.local'
5384 label = 'tags.local'
5386 tagtype = 'local'
5385 tagtype = 'local'
5387
5386
5388 fm.startitem()
5387 fm.startitem()
5389 fm.write('tag', '%s', t, label=label)
5388 fm.write('tag', '%s', t, label=label)
5390 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5389 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5391 fm.condwrite(not ui.quiet, 'rev node', fmt,
5390 fm.condwrite(not ui.quiet, 'rev node', fmt,
5392 repo.changelog.rev(n), hn, label=label)
5391 repo.changelog.rev(n), hn, label=label)
5393 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5392 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5394 tagtype, label=label)
5393 tagtype, label=label)
5395 fm.plain('\n')
5394 fm.plain('\n')
5396 fm.end()
5395 fm.end()
5397
5396
5398 @command('tip',
5397 @command('tip',
5399 [('p', 'patch', None, _('show patch')),
5398 [('p', 'patch', None, _('show patch')),
5400 ('g', 'git', None, _('use git extended diff format')),
5399 ('g', 'git', None, _('use git extended diff format')),
5401 ] + templateopts,
5400 ] + templateopts,
5402 _('[-p] [-g]'))
5401 _('[-p] [-g]'))
5403 def tip(ui, repo, **opts):
5402 def tip(ui, repo, **opts):
5404 """show the tip revision (DEPRECATED)
5403 """show the tip revision (DEPRECATED)
5405
5404
5406 The tip revision (usually just called the tip) is the changeset
5405 The tip revision (usually just called the tip) is the changeset
5407 most recently added to the repository (and therefore the most
5406 most recently added to the repository (and therefore the most
5408 recently changed head).
5407 recently changed head).
5409
5408
5410 If you have just made a commit, that commit will be the tip. If
5409 If you have just made a commit, that commit will be the tip. If
5411 you have just pulled changes from another repository, the tip of
5410 you have just pulled changes from another repository, the tip of
5412 that repository becomes the current tip. The "tip" tag is special
5411 that repository becomes the current tip. The "tip" tag is special
5413 and cannot be renamed or assigned to a different changeset.
5412 and cannot be renamed or assigned to a different changeset.
5414
5413
5415 This command is deprecated, please use :hg:`heads` instead.
5414 This command is deprecated, please use :hg:`heads` instead.
5416
5415
5417 Returns 0 on success.
5416 Returns 0 on success.
5418 """
5417 """
5419 opts = pycompat.byteskwargs(opts)
5418 opts = pycompat.byteskwargs(opts)
5420 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5419 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5421 displayer.show(repo['tip'])
5420 displayer.show(repo['tip'])
5422 displayer.close()
5421 displayer.close()
5423
5422
5424 @command('unbundle',
5423 @command('unbundle',
5425 [('u', 'update', None,
5424 [('u', 'update', None,
5426 _('update to new branch head if changesets were unbundled'))],
5425 _('update to new branch head if changesets were unbundled'))],
5427 _('[-u] FILE...'))
5426 _('[-u] FILE...'))
5428 def unbundle(ui, repo, fname1, *fnames, **opts):
5427 def unbundle(ui, repo, fname1, *fnames, **opts):
5429 """apply one or more bundle files
5428 """apply one or more bundle files
5430
5429
5431 Apply one or more bundle files generated by :hg:`bundle`.
5430 Apply one or more bundle files generated by :hg:`bundle`.
5432
5431
5433 Returns 0 on success, 1 if an update has unresolved files.
5432 Returns 0 on success, 1 if an update has unresolved files.
5434 """
5433 """
5435 fnames = (fname1,) + fnames
5434 fnames = (fname1,) + fnames
5436
5435
5437 with repo.lock():
5436 with repo.lock():
5438 for fname in fnames:
5437 for fname in fnames:
5439 f = hg.openpath(ui, fname)
5438 f = hg.openpath(ui, fname)
5440 gen = exchange.readbundle(ui, f, fname)
5439 gen = exchange.readbundle(ui, f, fname)
5441 if isinstance(gen, streamclone.streamcloneapplier):
5440 if isinstance(gen, streamclone.streamcloneapplier):
5442 raise error.Abort(
5441 raise error.Abort(
5443 _('packed bundles cannot be applied with '
5442 _('packed bundles cannot be applied with '
5444 '"hg unbundle"'),
5443 '"hg unbundle"'),
5445 hint=_('use "hg debugapplystreamclonebundle"'))
5444 hint=_('use "hg debugapplystreamclonebundle"'))
5446 url = 'bundle:' + fname
5445 url = 'bundle:' + fname
5447 try:
5446 try:
5448 txnname = 'unbundle'
5447 txnname = 'unbundle'
5449 if not isinstance(gen, bundle2.unbundle20):
5448 if not isinstance(gen, bundle2.unbundle20):
5450 txnname = 'unbundle\n%s' % util.hidepassword(url)
5449 txnname = 'unbundle\n%s' % util.hidepassword(url)
5451 with repo.transaction(txnname) as tr:
5450 with repo.transaction(txnname) as tr:
5452 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5451 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5453 url=url)
5452 url=url)
5454 except error.BundleUnknownFeatureError as exc:
5453 except error.BundleUnknownFeatureError as exc:
5455 raise error.Abort(
5454 raise error.Abort(
5456 _('%s: unknown bundle feature, %s') % (fname, exc),
5455 _('%s: unknown bundle feature, %s') % (fname, exc),
5457 hint=_("see https://mercurial-scm.org/"
5456 hint=_("see https://mercurial-scm.org/"
5458 "wiki/BundleFeature for more "
5457 "wiki/BundleFeature for more "
5459 "information"))
5458 "information"))
5460 modheads = bundle2.combinechangegroupresults(op)
5459 modheads = bundle2.combinechangegroupresults(op)
5461
5460
5462 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5461 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5463
5462
5464 @command('^update|up|checkout|co',
5463 @command('^update|up|checkout|co',
5465 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5464 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5466 ('c', 'check', None, _('require clean working directory')),
5465 ('c', 'check', None, _('require clean working directory')),
5467 ('m', 'merge', None, _('merge uncommitted changes')),
5466 ('m', 'merge', None, _('merge uncommitted changes')),
5468 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5467 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5469 ('r', 'rev', '', _('revision'), _('REV'))
5468 ('r', 'rev', '', _('revision'), _('REV'))
5470 ] + mergetoolopts,
5469 ] + mergetoolopts,
5471 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5470 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5472 def update(ui, repo, node=None, **opts):
5471 def update(ui, repo, node=None, **opts):
5473 """update working directory (or switch revisions)
5472 """update working directory (or switch revisions)
5474
5473
5475 Update the repository's working directory to the specified
5474 Update the repository's working directory to the specified
5476 changeset. If no changeset is specified, update to the tip of the
5475 changeset. If no changeset is specified, update to the tip of the
5477 current named branch and move the active bookmark (see :hg:`help
5476 current named branch and move the active bookmark (see :hg:`help
5478 bookmarks`).
5477 bookmarks`).
5479
5478
5480 Update sets the working directory's parent revision to the specified
5479 Update sets the working directory's parent revision to the specified
5481 changeset (see :hg:`help parents`).
5480 changeset (see :hg:`help parents`).
5482
5481
5483 If the changeset is not a descendant or ancestor of the working
5482 If the changeset is not a descendant or ancestor of the working
5484 directory's parent and there are uncommitted changes, the update is
5483 directory's parent and there are uncommitted changes, the update is
5485 aborted. With the -c/--check option, the working directory is checked
5484 aborted. With the -c/--check option, the working directory is checked
5486 for uncommitted changes; if none are found, the working directory is
5485 for uncommitted changes; if none are found, the working directory is
5487 updated to the specified changeset.
5486 updated to the specified changeset.
5488
5487
5489 .. container:: verbose
5488 .. container:: verbose
5490
5489
5491 The -C/--clean, -c/--check, and -m/--merge options control what
5490 The -C/--clean, -c/--check, and -m/--merge options control what
5492 happens if the working directory contains uncommitted changes.
5491 happens if the working directory contains uncommitted changes.
5493 At most of one of them can be specified.
5492 At most of one of them can be specified.
5494
5493
5495 1. If no option is specified, and if
5494 1. If no option is specified, and if
5496 the requested changeset is an ancestor or descendant of
5495 the requested changeset is an ancestor or descendant of
5497 the working directory's parent, the uncommitted changes
5496 the working directory's parent, the uncommitted changes
5498 are merged into the requested changeset and the merged
5497 are merged into the requested changeset and the merged
5499 result is left uncommitted. If the requested changeset is
5498 result is left uncommitted. If the requested changeset is
5500 not an ancestor or descendant (that is, it is on another
5499 not an ancestor or descendant (that is, it is on another
5501 branch), the update is aborted and the uncommitted changes
5500 branch), the update is aborted and the uncommitted changes
5502 are preserved.
5501 are preserved.
5503
5502
5504 2. With the -m/--merge option, the update is allowed even if the
5503 2. With the -m/--merge option, the update is allowed even if the
5505 requested changeset is not an ancestor or descendant of
5504 requested changeset is not an ancestor or descendant of
5506 the working directory's parent.
5505 the working directory's parent.
5507
5506
5508 3. With the -c/--check option, the update is aborted and the
5507 3. With the -c/--check option, the update is aborted and the
5509 uncommitted changes are preserved.
5508 uncommitted changes are preserved.
5510
5509
5511 4. With the -C/--clean option, uncommitted changes are discarded and
5510 4. With the -C/--clean option, uncommitted changes are discarded and
5512 the working directory is updated to the requested changeset.
5511 the working directory is updated to the requested changeset.
5513
5512
5514 To cancel an uncommitted merge (and lose your changes), use
5513 To cancel an uncommitted merge (and lose your changes), use
5515 :hg:`merge --abort`.
5514 :hg:`merge --abort`.
5516
5515
5517 Use null as the changeset to remove the working directory (like
5516 Use null as the changeset to remove the working directory (like
5518 :hg:`clone -U`).
5517 :hg:`clone -U`).
5519
5518
5520 If you want to revert just one file to an older revision, use
5519 If you want to revert just one file to an older revision, use
5521 :hg:`revert [-r REV] NAME`.
5520 :hg:`revert [-r REV] NAME`.
5522
5521
5523 See :hg:`help dates` for a list of formats valid for -d/--date.
5522 See :hg:`help dates` for a list of formats valid for -d/--date.
5524
5523
5525 Returns 0 on success, 1 if there are unresolved files.
5524 Returns 0 on success, 1 if there are unresolved files.
5526 """
5525 """
5527 rev = opts.get(r'rev')
5526 rev = opts.get(r'rev')
5528 date = opts.get(r'date')
5527 date = opts.get(r'date')
5529 clean = opts.get(r'clean')
5528 clean = opts.get(r'clean')
5530 check = opts.get(r'check')
5529 check = opts.get(r'check')
5531 merge = opts.get(r'merge')
5530 merge = opts.get(r'merge')
5532 if rev and node:
5531 if rev and node:
5533 raise error.Abort(_("please specify just one revision"))
5532 raise error.Abort(_("please specify just one revision"))
5534
5533
5535 if ui.configbool('commands', 'update.requiredest'):
5534 if ui.configbool('commands', 'update.requiredest'):
5536 if not node and not rev and not date:
5535 if not node and not rev and not date:
5537 raise error.Abort(_('you must specify a destination'),
5536 raise error.Abort(_('you must specify a destination'),
5538 hint=_('for example: hg update ".::"'))
5537 hint=_('for example: hg update ".::"'))
5539
5538
5540 if rev is None or rev == '':
5539 if rev is None or rev == '':
5541 rev = node
5540 rev = node
5542
5541
5543 if date and rev is not None:
5542 if date and rev is not None:
5544 raise error.Abort(_("you can't specify a revision and a date"))
5543 raise error.Abort(_("you can't specify a revision and a date"))
5545
5544
5546 if len([x for x in (clean, check, merge) if x]) > 1:
5545 if len([x for x in (clean, check, merge) if x]) > 1:
5547 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5546 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5548 "or -m/--merge"))
5547 "or -m/--merge"))
5549
5548
5550 updatecheck = None
5549 updatecheck = None
5551 if check:
5550 if check:
5552 updatecheck = 'abort'
5551 updatecheck = 'abort'
5553 elif merge:
5552 elif merge:
5554 updatecheck = 'none'
5553 updatecheck = 'none'
5555
5554
5556 with repo.wlock():
5555 with repo.wlock():
5557 cmdutil.clearunfinished(repo)
5556 cmdutil.clearunfinished(repo)
5558
5557
5559 if date:
5558 if date:
5560 rev = cmdutil.finddate(ui, repo, date)
5559 rev = cmdutil.finddate(ui, repo, date)
5561
5560
5562 # if we defined a bookmark, we have to remember the original name
5561 # if we defined a bookmark, we have to remember the original name
5563 brev = rev
5562 brev = rev
5564 if rev:
5563 if rev:
5565 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5564 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5566 ctx = scmutil.revsingle(repo, rev, rev)
5565 ctx = scmutil.revsingle(repo, rev, rev)
5567 rev = ctx.rev()
5566 rev = ctx.rev()
5568 if ctx.hidden():
5567 if ctx.hidden():
5569 ctxstr = ctx.hex()[:12]
5568 ctxstr = ctx.hex()[:12]
5570 ui.warn(_("updating to a hidden changeset %s\n") % ctxstr)
5569 ui.warn(_("updating to a hidden changeset %s\n") % ctxstr)
5571
5570
5572 if ctx.obsolete():
5571 if ctx.obsolete():
5573 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
5572 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
5574 ui.warn("(%s)\n" % obsfatemsg)
5573 ui.warn("(%s)\n" % obsfatemsg)
5575
5574
5576 repo.ui.setconfig('ui', 'forcemerge', opts.get(r'tool'), 'update')
5575 repo.ui.setconfig('ui', 'forcemerge', opts.get(r'tool'), 'update')
5577
5576
5578 return hg.updatetotally(ui, repo, rev, brev, clean=clean,
5577 return hg.updatetotally(ui, repo, rev, brev, clean=clean,
5579 updatecheck=updatecheck)
5578 updatecheck=updatecheck)
5580
5579
5581 @command('verify', [])
5580 @command('verify', [])
5582 def verify(ui, repo):
5581 def verify(ui, repo):
5583 """verify the integrity of the repository
5582 """verify the integrity of the repository
5584
5583
5585 Verify the integrity of the current repository.
5584 Verify the integrity of the current repository.
5586
5585
5587 This will perform an extensive check of the repository's
5586 This will perform an extensive check of the repository's
5588 integrity, validating the hashes and checksums of each entry in
5587 integrity, validating the hashes and checksums of each entry in
5589 the changelog, manifest, and tracked files, as well as the
5588 the changelog, manifest, and tracked files, as well as the
5590 integrity of their crosslinks and indices.
5589 integrity of their crosslinks and indices.
5591
5590
5592 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5591 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5593 for more information about recovery from corruption of the
5592 for more information about recovery from corruption of the
5594 repository.
5593 repository.
5595
5594
5596 Returns 0 on success, 1 if errors are encountered.
5595 Returns 0 on success, 1 if errors are encountered.
5597 """
5596 """
5598 return hg.verify(repo)
5597 return hg.verify(repo)
5599
5598
5600 @command('version', [] + formatteropts, norepo=True, cmdtype=readonly)
5599 @command('version', [] + formatteropts, norepo=True, cmdtype=readonly)
5601 def version_(ui, **opts):
5600 def version_(ui, **opts):
5602 """output version and copyright information"""
5601 """output version and copyright information"""
5603 opts = pycompat.byteskwargs(opts)
5602 opts = pycompat.byteskwargs(opts)
5604 if ui.verbose:
5603 if ui.verbose:
5605 ui.pager('version')
5604 ui.pager('version')
5606 fm = ui.formatter("version", opts)
5605 fm = ui.formatter("version", opts)
5607 fm.startitem()
5606 fm.startitem()
5608 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5607 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5609 util.version())
5608 util.version())
5610 license = _(
5609 license = _(
5611 "(see https://mercurial-scm.org for more information)\n"
5610 "(see https://mercurial-scm.org for more information)\n"
5612 "\nCopyright (C) 2005-2018 Matt Mackall and others\n"
5611 "\nCopyright (C) 2005-2018 Matt Mackall and others\n"
5613 "This is free software; see the source for copying conditions. "
5612 "This is free software; see the source for copying conditions. "
5614 "There is NO\nwarranty; "
5613 "There is NO\nwarranty; "
5615 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5614 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5616 )
5615 )
5617 if not ui.quiet:
5616 if not ui.quiet:
5618 fm.plain(license)
5617 fm.plain(license)
5619
5618
5620 if ui.verbose:
5619 if ui.verbose:
5621 fm.plain(_("\nEnabled extensions:\n\n"))
5620 fm.plain(_("\nEnabled extensions:\n\n"))
5622 # format names and versions into columns
5621 # format names and versions into columns
5623 names = []
5622 names = []
5624 vers = []
5623 vers = []
5625 isinternals = []
5624 isinternals = []
5626 for name, module in extensions.extensions():
5625 for name, module in extensions.extensions():
5627 names.append(name)
5626 names.append(name)
5628 vers.append(extensions.moduleversion(module) or None)
5627 vers.append(extensions.moduleversion(module) or None)
5629 isinternals.append(extensions.ismoduleinternal(module))
5628 isinternals.append(extensions.ismoduleinternal(module))
5630 fn = fm.nested("extensions", tmpl='{name}\n')
5629 fn = fm.nested("extensions", tmpl='{name}\n')
5631 if names:
5630 if names:
5632 namefmt = " %%-%ds " % max(len(n) for n in names)
5631 namefmt = " %%-%ds " % max(len(n) for n in names)
5633 places = [_("external"), _("internal")]
5632 places = [_("external"), _("internal")]
5634 for n, v, p in zip(names, vers, isinternals):
5633 for n, v, p in zip(names, vers, isinternals):
5635 fn.startitem()
5634 fn.startitem()
5636 fn.condwrite(ui.verbose, "name", namefmt, n)
5635 fn.condwrite(ui.verbose, "name", namefmt, n)
5637 if ui.verbose:
5636 if ui.verbose:
5638 fn.plain("%s " % places[p])
5637 fn.plain("%s " % places[p])
5639 fn.data(bundled=p)
5638 fn.data(bundled=p)
5640 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5639 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5641 if ui.verbose:
5640 if ui.verbose:
5642 fn.plain("\n")
5641 fn.plain("\n")
5643 fn.end()
5642 fn.end()
5644 fm.end()
5643 fm.end()
5645
5644
5646 def loadcmdtable(ui, name, cmdtable):
5645 def loadcmdtable(ui, name, cmdtable):
5647 """Load command functions from specified cmdtable
5646 """Load command functions from specified cmdtable
5648 """
5647 """
5649 overrides = [cmd for cmd in cmdtable if cmd in table]
5648 overrides = [cmd for cmd in cmdtable if cmd in table]
5650 if overrides:
5649 if overrides:
5651 ui.warn(_("extension '%s' overrides commands: %s\n")
5650 ui.warn(_("extension '%s' overrides commands: %s\n")
5652 % (name, " ".join(overrides)))
5651 % (name, " ".join(overrides)))
5653 table.update(cmdtable)
5652 table.update(cmdtable)
@@ -1,2915 +1,2920 b''
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import, print_function
9 from __future__ import absolute_import, print_function
10
10
11 import collections
11 import collections
12 import contextlib
12 import copy
13 import copy
13 import difflib
14 import difflib
14 import email
15 import email
15 import errno
16 import errno
16 import hashlib
17 import hashlib
17 import os
18 import os
18 import posixpath
19 import posixpath
19 import re
20 import re
20 import shutil
21 import shutil
21 import tempfile
22 import tempfile
22 import zlib
23 import zlib
23
24
24 from .i18n import _
25 from .i18n import _
25 from .node import (
26 from .node import (
26 hex,
27 hex,
27 short,
28 short,
28 )
29 )
29 from . import (
30 from . import (
30 copies,
31 copies,
31 diffhelpers,
32 diffhelpers,
32 encoding,
33 encoding,
33 error,
34 error,
34 mail,
35 mail,
35 mdiff,
36 mdiff,
36 pathutil,
37 pathutil,
37 pycompat,
38 pycompat,
38 scmutil,
39 scmutil,
39 similar,
40 similar,
40 util,
41 util,
41 vfs as vfsmod,
42 vfs as vfsmod,
42 )
43 )
43 from .utils import (
44 from .utils import (
44 dateutil,
45 dateutil,
45 procutil,
46 procutil,
46 stringutil,
47 stringutil,
47 )
48 )
48
49
49 stringio = util.stringio
50 stringio = util.stringio
50
51
51 gitre = re.compile(br'diff --git a/(.*) b/(.*)')
52 gitre = re.compile(br'diff --git a/(.*) b/(.*)')
52 tabsplitter = re.compile(br'(\t+|[^\t]+)')
53 tabsplitter = re.compile(br'(\t+|[^\t]+)')
53 _nonwordre = re.compile(br'([^a-zA-Z0-9_\x80-\xff])')
54 _nonwordre = re.compile(br'([^a-zA-Z0-9_\x80-\xff])')
54
55
55 PatchError = error.PatchError
56 PatchError = error.PatchError
56
57
57 # public functions
58 # public functions
58
59
59 def split(stream):
60 def split(stream):
60 '''return an iterator of individual patches from a stream'''
61 '''return an iterator of individual patches from a stream'''
61 def isheader(line, inheader):
62 def isheader(line, inheader):
62 if inheader and line.startswith((' ', '\t')):
63 if inheader and line.startswith((' ', '\t')):
63 # continuation
64 # continuation
64 return True
65 return True
65 if line.startswith((' ', '-', '+')):
66 if line.startswith((' ', '-', '+')):
66 # diff line - don't check for header pattern in there
67 # diff line - don't check for header pattern in there
67 return False
68 return False
68 l = line.split(': ', 1)
69 l = line.split(': ', 1)
69 return len(l) == 2 and ' ' not in l[0]
70 return len(l) == 2 and ' ' not in l[0]
70
71
71 def chunk(lines):
72 def chunk(lines):
72 return stringio(''.join(lines))
73 return stringio(''.join(lines))
73
74
74 def hgsplit(stream, cur):
75 def hgsplit(stream, cur):
75 inheader = True
76 inheader = True
76
77
77 for line in stream:
78 for line in stream:
78 if not line.strip():
79 if not line.strip():
79 inheader = False
80 inheader = False
80 if not inheader and line.startswith('# HG changeset patch'):
81 if not inheader and line.startswith('# HG changeset patch'):
81 yield chunk(cur)
82 yield chunk(cur)
82 cur = []
83 cur = []
83 inheader = True
84 inheader = True
84
85
85 cur.append(line)
86 cur.append(line)
86
87
87 if cur:
88 if cur:
88 yield chunk(cur)
89 yield chunk(cur)
89
90
90 def mboxsplit(stream, cur):
91 def mboxsplit(stream, cur):
91 for line in stream:
92 for line in stream:
92 if line.startswith('From '):
93 if line.startswith('From '):
93 for c in split(chunk(cur[1:])):
94 for c in split(chunk(cur[1:])):
94 yield c
95 yield c
95 cur = []
96 cur = []
96
97
97 cur.append(line)
98 cur.append(line)
98
99
99 if cur:
100 if cur:
100 for c in split(chunk(cur[1:])):
101 for c in split(chunk(cur[1:])):
101 yield c
102 yield c
102
103
103 def mimesplit(stream, cur):
104 def mimesplit(stream, cur):
104 def msgfp(m):
105 def msgfp(m):
105 fp = stringio()
106 fp = stringio()
106 g = email.Generator.Generator(fp, mangle_from_=False)
107 g = email.Generator.Generator(fp, mangle_from_=False)
107 g.flatten(m)
108 g.flatten(m)
108 fp.seek(0)
109 fp.seek(0)
109 return fp
110 return fp
110
111
111 for line in stream:
112 for line in stream:
112 cur.append(line)
113 cur.append(line)
113 c = chunk(cur)
114 c = chunk(cur)
114
115
115 m = pycompat.emailparser().parse(c)
116 m = pycompat.emailparser().parse(c)
116 if not m.is_multipart():
117 if not m.is_multipart():
117 yield msgfp(m)
118 yield msgfp(m)
118 else:
119 else:
119 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
120 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
120 for part in m.walk():
121 for part in m.walk():
121 ct = part.get_content_type()
122 ct = part.get_content_type()
122 if ct not in ok_types:
123 if ct not in ok_types:
123 continue
124 continue
124 yield msgfp(part)
125 yield msgfp(part)
125
126
126 def headersplit(stream, cur):
127 def headersplit(stream, cur):
127 inheader = False
128 inheader = False
128
129
129 for line in stream:
130 for line in stream:
130 if not inheader and isheader(line, inheader):
131 if not inheader and isheader(line, inheader):
131 yield chunk(cur)
132 yield chunk(cur)
132 cur = []
133 cur = []
133 inheader = True
134 inheader = True
134 if inheader and not isheader(line, inheader):
135 if inheader and not isheader(line, inheader):
135 inheader = False
136 inheader = False
136
137
137 cur.append(line)
138 cur.append(line)
138
139
139 if cur:
140 if cur:
140 yield chunk(cur)
141 yield chunk(cur)
141
142
142 def remainder(cur):
143 def remainder(cur):
143 yield chunk(cur)
144 yield chunk(cur)
144
145
145 class fiter(object):
146 class fiter(object):
146 def __init__(self, fp):
147 def __init__(self, fp):
147 self.fp = fp
148 self.fp = fp
148
149
149 def __iter__(self):
150 def __iter__(self):
150 return self
151 return self
151
152
152 def next(self):
153 def next(self):
153 l = self.fp.readline()
154 l = self.fp.readline()
154 if not l:
155 if not l:
155 raise StopIteration
156 raise StopIteration
156 return l
157 return l
157
158
158 __next__ = next
159 __next__ = next
159
160
160 inheader = False
161 inheader = False
161 cur = []
162 cur = []
162
163
163 mimeheaders = ['content-type']
164 mimeheaders = ['content-type']
164
165
165 if not util.safehasattr(stream, 'next'):
166 if not util.safehasattr(stream, 'next'):
166 # http responses, for example, have readline but not next
167 # http responses, for example, have readline but not next
167 stream = fiter(stream)
168 stream = fiter(stream)
168
169
169 for line in stream:
170 for line in stream:
170 cur.append(line)
171 cur.append(line)
171 if line.startswith('# HG changeset patch'):
172 if line.startswith('# HG changeset patch'):
172 return hgsplit(stream, cur)
173 return hgsplit(stream, cur)
173 elif line.startswith('From '):
174 elif line.startswith('From '):
174 return mboxsplit(stream, cur)
175 return mboxsplit(stream, cur)
175 elif isheader(line, inheader):
176 elif isheader(line, inheader):
176 inheader = True
177 inheader = True
177 if line.split(':', 1)[0].lower() in mimeheaders:
178 if line.split(':', 1)[0].lower() in mimeheaders:
178 # let email parser handle this
179 # let email parser handle this
179 return mimesplit(stream, cur)
180 return mimesplit(stream, cur)
180 elif line.startswith('--- ') and inheader:
181 elif line.startswith('--- ') and inheader:
181 # No evil headers seen by diff start, split by hand
182 # No evil headers seen by diff start, split by hand
182 return headersplit(stream, cur)
183 return headersplit(stream, cur)
183 # Not enough info, keep reading
184 # Not enough info, keep reading
184
185
185 # if we are here, we have a very plain patch
186 # if we are here, we have a very plain patch
186 return remainder(cur)
187 return remainder(cur)
187
188
188 ## Some facility for extensible patch parsing:
189 ## Some facility for extensible patch parsing:
189 # list of pairs ("header to match", "data key")
190 # list of pairs ("header to match", "data key")
190 patchheadermap = [('Date', 'date'),
191 patchheadermap = [('Date', 'date'),
191 ('Branch', 'branch'),
192 ('Branch', 'branch'),
192 ('Node ID', 'nodeid'),
193 ('Node ID', 'nodeid'),
193 ]
194 ]
194
195
196 @contextlib.contextmanager
195 def extract(ui, fileobj):
197 def extract(ui, fileobj):
196 '''extract patch from data read from fileobj.
198 '''extract patch from data read from fileobj.
197
199
198 patch can be a normal patch or contained in an email message.
200 patch can be a normal patch or contained in an email message.
199
201
200 return a dictionary. Standard keys are:
202 return a dictionary. Standard keys are:
201 - filename,
203 - filename,
202 - message,
204 - message,
203 - user,
205 - user,
204 - date,
206 - date,
205 - branch,
207 - branch,
206 - node,
208 - node,
207 - p1,
209 - p1,
208 - p2.
210 - p2.
209 Any item can be missing from the dictionary. If filename is missing,
211 Any item can be missing from the dictionary. If filename is missing,
210 fileobj did not contain a patch. Caller must unlink filename when done.'''
212 fileobj did not contain a patch. Caller must unlink filename when done.'''
211
213
214 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
215 tmpfp = os.fdopen(fd, r'wb')
216 try:
217 yield _extract(ui, fileobj, tmpname, tmpfp)
218 finally:
219 tmpfp.close()
220 os.unlink(tmpname)
221
222 def _extract(ui, fileobj, tmpname, tmpfp):
223
212 # attempt to detect the start of a patch
224 # attempt to detect the start of a patch
213 # (this heuristic is borrowed from quilt)
225 # (this heuristic is borrowed from quilt)
214 diffre = re.compile(br'^(?:Index:[ \t]|diff[ \t]-|RCS file: |'
226 diffre = re.compile(br'^(?:Index:[ \t]|diff[ \t]-|RCS file: |'
215 br'retrieving revision [0-9]+(\.[0-9]+)*$|'
227 br'retrieving revision [0-9]+(\.[0-9]+)*$|'
216 br'---[ \t].*?^\+\+\+[ \t]|'
228 br'---[ \t].*?^\+\+\+[ \t]|'
217 br'\*\*\*[ \t].*?^---[ \t])',
229 br'\*\*\*[ \t].*?^---[ \t])',
218 re.MULTILINE | re.DOTALL)
230 re.MULTILINE | re.DOTALL)
219
231
220 data = {}
232 data = {}
221 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
233
222 tmpfp = os.fdopen(fd, r'wb')
234 msg = pycompat.emailparser().parse(fileobj)
223 try:
224 msg = pycompat.emailparser().parse(fileobj)
225
235
226 subject = msg[r'Subject'] and mail.headdecode(msg[r'Subject'])
236 subject = msg[r'Subject'] and mail.headdecode(msg[r'Subject'])
227 data['user'] = msg[r'From'] and mail.headdecode(msg[r'From'])
237 data['user'] = msg[r'From'] and mail.headdecode(msg[r'From'])
228 if not subject and not data['user']:
238 if not subject and not data['user']:
229 # Not an email, restore parsed headers if any
239 # Not an email, restore parsed headers if any
230 subject = '\n'.join(': '.join(map(encoding.strtolocal, h))
240 subject = '\n'.join(': '.join(map(encoding.strtolocal, h))
231 for h in msg.items()) + '\n'
241 for h in msg.items()) + '\n'
232
242
233 # should try to parse msg['Date']
243 # should try to parse msg['Date']
234 parents = []
244 parents = []
235
245
236 if subject:
246 if subject:
237 if subject.startswith('[PATCH'):
247 if subject.startswith('[PATCH'):
238 pend = subject.find(']')
248 pend = subject.find(']')
239 if pend >= 0:
249 if pend >= 0:
240 subject = subject[pend + 1:].lstrip()
250 subject = subject[pend + 1:].lstrip()
241 subject = re.sub(br'\n[ \t]+', ' ', subject)
251 subject = re.sub(br'\n[ \t]+', ' ', subject)
242 ui.debug('Subject: %s\n' % subject)
252 ui.debug('Subject: %s\n' % subject)
243 if data['user']:
253 if data['user']:
244 ui.debug('From: %s\n' % data['user'])
254 ui.debug('From: %s\n' % data['user'])
245 diffs_seen = 0
255 diffs_seen = 0
246 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
256 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
247 message = ''
257 message = ''
248 for part in msg.walk():
258 for part in msg.walk():
249 content_type = pycompat.bytestr(part.get_content_type())
259 content_type = pycompat.bytestr(part.get_content_type())
250 ui.debug('Content-Type: %s\n' % content_type)
260 ui.debug('Content-Type: %s\n' % content_type)
251 if content_type not in ok_types:
261 if content_type not in ok_types:
252 continue
262 continue
253 payload = part.get_payload(decode=True)
263 payload = part.get_payload(decode=True)
254 m = diffre.search(payload)
264 m = diffre.search(payload)
255 if m:
265 if m:
256 hgpatch = False
266 hgpatch = False
257 hgpatchheader = False
267 hgpatchheader = False
258 ignoretext = False
268 ignoretext = False
259
269
260 ui.debug('found patch at byte %d\n' % m.start(0))
270 ui.debug('found patch at byte %d\n' % m.start(0))
261 diffs_seen += 1
271 diffs_seen += 1
262 cfp = stringio()
272 cfp = stringio()
263 for line in payload[:m.start(0)].splitlines():
273 for line in payload[:m.start(0)].splitlines():
264 if line.startswith('# HG changeset patch') and not hgpatch:
274 if line.startswith('# HG changeset patch') and not hgpatch:
265 ui.debug('patch generated by hg export\n')
275 ui.debug('patch generated by hg export\n')
266 hgpatch = True
276 hgpatch = True
267 hgpatchheader = True
277 hgpatchheader = True
268 # drop earlier commit message content
278 # drop earlier commit message content
269 cfp.seek(0)
279 cfp.seek(0)
270 cfp.truncate()
280 cfp.truncate()
271 subject = None
281 subject = None
272 elif hgpatchheader:
282 elif hgpatchheader:
273 if line.startswith('# User '):
283 if line.startswith('# User '):
274 data['user'] = line[7:]
284 data['user'] = line[7:]
275 ui.debug('From: %s\n' % data['user'])
285 ui.debug('From: %s\n' % data['user'])
276 elif line.startswith("# Parent "):
286 elif line.startswith("# Parent "):
277 parents.append(line[9:].lstrip())
287 parents.append(line[9:].lstrip())
278 elif line.startswith("# "):
288 elif line.startswith("# "):
279 for header, key in patchheadermap:
289 for header, key in patchheadermap:
280 prefix = '# %s ' % header
290 prefix = '# %s ' % header
281 if line.startswith(prefix):
291 if line.startswith(prefix):
282 data[key] = line[len(prefix):]
292 data[key] = line[len(prefix):]
283 else:
293 else:
284 hgpatchheader = False
294 hgpatchheader = False
285 elif line == '---':
295 elif line == '---':
286 ignoretext = True
296 ignoretext = True
287 if not hgpatchheader and not ignoretext:
297 if not hgpatchheader and not ignoretext:
288 cfp.write(line)
298 cfp.write(line)
289 cfp.write('\n')
299 cfp.write('\n')
290 message = cfp.getvalue()
300 message = cfp.getvalue()
291 if tmpfp:
301 if tmpfp:
292 tmpfp.write(payload)
302 tmpfp.write(payload)
293 if not payload.endswith('\n'):
303 if not payload.endswith('\n'):
294 tmpfp.write('\n')
304 tmpfp.write('\n')
295 elif not diffs_seen and message and content_type == 'text/plain':
305 elif not diffs_seen and message and content_type == 'text/plain':
296 message += '\n' + payload
306 message += '\n' + payload
297 except: # re-raises
298 tmpfp.close()
299 os.unlink(tmpname)
300 raise
301
307
302 if subject and not message.startswith(subject):
308 if subject and not message.startswith(subject):
303 message = '%s\n%s' % (subject, message)
309 message = '%s\n%s' % (subject, message)
304 data['message'] = message
310 data['message'] = message
305 tmpfp.close()
311 tmpfp.close()
306 if parents:
312 if parents:
307 data['p1'] = parents.pop(0)
313 data['p1'] = parents.pop(0)
308 if parents:
314 if parents:
309 data['p2'] = parents.pop(0)
315 data['p2'] = parents.pop(0)
310
316
311 if diffs_seen:
317 if diffs_seen:
312 data['filename'] = tmpname
318 data['filename'] = tmpname
313 else:
319
314 os.unlink(tmpname)
315 return data
320 return data
316
321
317 class patchmeta(object):
322 class patchmeta(object):
318 """Patched file metadata
323 """Patched file metadata
319
324
320 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
325 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
321 or COPY. 'path' is patched file path. 'oldpath' is set to the
326 or COPY. 'path' is patched file path. 'oldpath' is set to the
322 origin file when 'op' is either COPY or RENAME, None otherwise. If
327 origin file when 'op' is either COPY or RENAME, None otherwise. If
323 file mode is changed, 'mode' is a tuple (islink, isexec) where
328 file mode is changed, 'mode' is a tuple (islink, isexec) where
324 'islink' is True if the file is a symlink and 'isexec' is True if
329 'islink' is True if the file is a symlink and 'isexec' is True if
325 the file is executable. Otherwise, 'mode' is None.
330 the file is executable. Otherwise, 'mode' is None.
326 """
331 """
327 def __init__(self, path):
332 def __init__(self, path):
328 self.path = path
333 self.path = path
329 self.oldpath = None
334 self.oldpath = None
330 self.mode = None
335 self.mode = None
331 self.op = 'MODIFY'
336 self.op = 'MODIFY'
332 self.binary = False
337 self.binary = False
333
338
334 def setmode(self, mode):
339 def setmode(self, mode):
335 islink = mode & 0o20000
340 islink = mode & 0o20000
336 isexec = mode & 0o100
341 isexec = mode & 0o100
337 self.mode = (islink, isexec)
342 self.mode = (islink, isexec)
338
343
339 def copy(self):
344 def copy(self):
340 other = patchmeta(self.path)
345 other = patchmeta(self.path)
341 other.oldpath = self.oldpath
346 other.oldpath = self.oldpath
342 other.mode = self.mode
347 other.mode = self.mode
343 other.op = self.op
348 other.op = self.op
344 other.binary = self.binary
349 other.binary = self.binary
345 return other
350 return other
346
351
347 def _ispatchinga(self, afile):
352 def _ispatchinga(self, afile):
348 if afile == '/dev/null':
353 if afile == '/dev/null':
349 return self.op == 'ADD'
354 return self.op == 'ADD'
350 return afile == 'a/' + (self.oldpath or self.path)
355 return afile == 'a/' + (self.oldpath or self.path)
351
356
352 def _ispatchingb(self, bfile):
357 def _ispatchingb(self, bfile):
353 if bfile == '/dev/null':
358 if bfile == '/dev/null':
354 return self.op == 'DELETE'
359 return self.op == 'DELETE'
355 return bfile == 'b/' + self.path
360 return bfile == 'b/' + self.path
356
361
357 def ispatching(self, afile, bfile):
362 def ispatching(self, afile, bfile):
358 return self._ispatchinga(afile) and self._ispatchingb(bfile)
363 return self._ispatchinga(afile) and self._ispatchingb(bfile)
359
364
360 def __repr__(self):
365 def __repr__(self):
361 return "<patchmeta %s %r>" % (self.op, self.path)
366 return "<patchmeta %s %r>" % (self.op, self.path)
362
367
363 def readgitpatch(lr):
368 def readgitpatch(lr):
364 """extract git-style metadata about patches from <patchname>"""
369 """extract git-style metadata about patches from <patchname>"""
365
370
366 # Filter patch for git information
371 # Filter patch for git information
367 gp = None
372 gp = None
368 gitpatches = []
373 gitpatches = []
369 for line in lr:
374 for line in lr:
370 line = line.rstrip(' \r\n')
375 line = line.rstrip(' \r\n')
371 if line.startswith('diff --git a/'):
376 if line.startswith('diff --git a/'):
372 m = gitre.match(line)
377 m = gitre.match(line)
373 if m:
378 if m:
374 if gp:
379 if gp:
375 gitpatches.append(gp)
380 gitpatches.append(gp)
376 dst = m.group(2)
381 dst = m.group(2)
377 gp = patchmeta(dst)
382 gp = patchmeta(dst)
378 elif gp:
383 elif gp:
379 if line.startswith('--- '):
384 if line.startswith('--- '):
380 gitpatches.append(gp)
385 gitpatches.append(gp)
381 gp = None
386 gp = None
382 continue
387 continue
383 if line.startswith('rename from '):
388 if line.startswith('rename from '):
384 gp.op = 'RENAME'
389 gp.op = 'RENAME'
385 gp.oldpath = line[12:]
390 gp.oldpath = line[12:]
386 elif line.startswith('rename to '):
391 elif line.startswith('rename to '):
387 gp.path = line[10:]
392 gp.path = line[10:]
388 elif line.startswith('copy from '):
393 elif line.startswith('copy from '):
389 gp.op = 'COPY'
394 gp.op = 'COPY'
390 gp.oldpath = line[10:]
395 gp.oldpath = line[10:]
391 elif line.startswith('copy to '):
396 elif line.startswith('copy to '):
392 gp.path = line[8:]
397 gp.path = line[8:]
393 elif line.startswith('deleted file'):
398 elif line.startswith('deleted file'):
394 gp.op = 'DELETE'
399 gp.op = 'DELETE'
395 elif line.startswith('new file mode '):
400 elif line.startswith('new file mode '):
396 gp.op = 'ADD'
401 gp.op = 'ADD'
397 gp.setmode(int(line[-6:], 8))
402 gp.setmode(int(line[-6:], 8))
398 elif line.startswith('new mode '):
403 elif line.startswith('new mode '):
399 gp.setmode(int(line[-6:], 8))
404 gp.setmode(int(line[-6:], 8))
400 elif line.startswith('GIT binary patch'):
405 elif line.startswith('GIT binary patch'):
401 gp.binary = True
406 gp.binary = True
402 if gp:
407 if gp:
403 gitpatches.append(gp)
408 gitpatches.append(gp)
404
409
405 return gitpatches
410 return gitpatches
406
411
407 class linereader(object):
412 class linereader(object):
408 # simple class to allow pushing lines back into the input stream
413 # simple class to allow pushing lines back into the input stream
409 def __init__(self, fp):
414 def __init__(self, fp):
410 self.fp = fp
415 self.fp = fp
411 self.buf = []
416 self.buf = []
412
417
413 def push(self, line):
418 def push(self, line):
414 if line is not None:
419 if line is not None:
415 self.buf.append(line)
420 self.buf.append(line)
416
421
417 def readline(self):
422 def readline(self):
418 if self.buf:
423 if self.buf:
419 l = self.buf[0]
424 l = self.buf[0]
420 del self.buf[0]
425 del self.buf[0]
421 return l
426 return l
422 return self.fp.readline()
427 return self.fp.readline()
423
428
424 def __iter__(self):
429 def __iter__(self):
425 return iter(self.readline, '')
430 return iter(self.readline, '')
426
431
427 class abstractbackend(object):
432 class abstractbackend(object):
428 def __init__(self, ui):
433 def __init__(self, ui):
429 self.ui = ui
434 self.ui = ui
430
435
431 def getfile(self, fname):
436 def getfile(self, fname):
432 """Return target file data and flags as a (data, (islink,
437 """Return target file data and flags as a (data, (islink,
433 isexec)) tuple. Data is None if file is missing/deleted.
438 isexec)) tuple. Data is None if file is missing/deleted.
434 """
439 """
435 raise NotImplementedError
440 raise NotImplementedError
436
441
437 def setfile(self, fname, data, mode, copysource):
442 def setfile(self, fname, data, mode, copysource):
438 """Write data to target file fname and set its mode. mode is a
443 """Write data to target file fname and set its mode. mode is a
439 (islink, isexec) tuple. If data is None, the file content should
444 (islink, isexec) tuple. If data is None, the file content should
440 be left unchanged. If the file is modified after being copied,
445 be left unchanged. If the file is modified after being copied,
441 copysource is set to the original file name.
446 copysource is set to the original file name.
442 """
447 """
443 raise NotImplementedError
448 raise NotImplementedError
444
449
445 def unlink(self, fname):
450 def unlink(self, fname):
446 """Unlink target file."""
451 """Unlink target file."""
447 raise NotImplementedError
452 raise NotImplementedError
448
453
449 def writerej(self, fname, failed, total, lines):
454 def writerej(self, fname, failed, total, lines):
450 """Write rejected lines for fname. total is the number of hunks
455 """Write rejected lines for fname. total is the number of hunks
451 which failed to apply and total the total number of hunks for this
456 which failed to apply and total the total number of hunks for this
452 files.
457 files.
453 """
458 """
454
459
455 def exists(self, fname):
460 def exists(self, fname):
456 raise NotImplementedError
461 raise NotImplementedError
457
462
458 def close(self):
463 def close(self):
459 raise NotImplementedError
464 raise NotImplementedError
460
465
461 class fsbackend(abstractbackend):
466 class fsbackend(abstractbackend):
462 def __init__(self, ui, basedir):
467 def __init__(self, ui, basedir):
463 super(fsbackend, self).__init__(ui)
468 super(fsbackend, self).__init__(ui)
464 self.opener = vfsmod.vfs(basedir)
469 self.opener = vfsmod.vfs(basedir)
465
470
466 def getfile(self, fname):
471 def getfile(self, fname):
467 if self.opener.islink(fname):
472 if self.opener.islink(fname):
468 return (self.opener.readlink(fname), (True, False))
473 return (self.opener.readlink(fname), (True, False))
469
474
470 isexec = False
475 isexec = False
471 try:
476 try:
472 isexec = self.opener.lstat(fname).st_mode & 0o100 != 0
477 isexec = self.opener.lstat(fname).st_mode & 0o100 != 0
473 except OSError as e:
478 except OSError as e:
474 if e.errno != errno.ENOENT:
479 if e.errno != errno.ENOENT:
475 raise
480 raise
476 try:
481 try:
477 return (self.opener.read(fname), (False, isexec))
482 return (self.opener.read(fname), (False, isexec))
478 except IOError as e:
483 except IOError as e:
479 if e.errno != errno.ENOENT:
484 if e.errno != errno.ENOENT:
480 raise
485 raise
481 return None, None
486 return None, None
482
487
483 def setfile(self, fname, data, mode, copysource):
488 def setfile(self, fname, data, mode, copysource):
484 islink, isexec = mode
489 islink, isexec = mode
485 if data is None:
490 if data is None:
486 self.opener.setflags(fname, islink, isexec)
491 self.opener.setflags(fname, islink, isexec)
487 return
492 return
488 if islink:
493 if islink:
489 self.opener.symlink(data, fname)
494 self.opener.symlink(data, fname)
490 else:
495 else:
491 self.opener.write(fname, data)
496 self.opener.write(fname, data)
492 if isexec:
497 if isexec:
493 self.opener.setflags(fname, False, True)
498 self.opener.setflags(fname, False, True)
494
499
495 def unlink(self, fname):
500 def unlink(self, fname):
496 self.opener.unlinkpath(fname, ignoremissing=True)
501 self.opener.unlinkpath(fname, ignoremissing=True)
497
502
498 def writerej(self, fname, failed, total, lines):
503 def writerej(self, fname, failed, total, lines):
499 fname = fname + ".rej"
504 fname = fname + ".rej"
500 self.ui.warn(
505 self.ui.warn(
501 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
506 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
502 (failed, total, fname))
507 (failed, total, fname))
503 fp = self.opener(fname, 'w')
508 fp = self.opener(fname, 'w')
504 fp.writelines(lines)
509 fp.writelines(lines)
505 fp.close()
510 fp.close()
506
511
507 def exists(self, fname):
512 def exists(self, fname):
508 return self.opener.lexists(fname)
513 return self.opener.lexists(fname)
509
514
510 class workingbackend(fsbackend):
515 class workingbackend(fsbackend):
511 def __init__(self, ui, repo, similarity):
516 def __init__(self, ui, repo, similarity):
512 super(workingbackend, self).__init__(ui, repo.root)
517 super(workingbackend, self).__init__(ui, repo.root)
513 self.repo = repo
518 self.repo = repo
514 self.similarity = similarity
519 self.similarity = similarity
515 self.removed = set()
520 self.removed = set()
516 self.changed = set()
521 self.changed = set()
517 self.copied = []
522 self.copied = []
518
523
519 def _checkknown(self, fname):
524 def _checkknown(self, fname):
520 if self.repo.dirstate[fname] == '?' and self.exists(fname):
525 if self.repo.dirstate[fname] == '?' and self.exists(fname):
521 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
526 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
522
527
523 def setfile(self, fname, data, mode, copysource):
528 def setfile(self, fname, data, mode, copysource):
524 self._checkknown(fname)
529 self._checkknown(fname)
525 super(workingbackend, self).setfile(fname, data, mode, copysource)
530 super(workingbackend, self).setfile(fname, data, mode, copysource)
526 if copysource is not None:
531 if copysource is not None:
527 self.copied.append((copysource, fname))
532 self.copied.append((copysource, fname))
528 self.changed.add(fname)
533 self.changed.add(fname)
529
534
530 def unlink(self, fname):
535 def unlink(self, fname):
531 self._checkknown(fname)
536 self._checkknown(fname)
532 super(workingbackend, self).unlink(fname)
537 super(workingbackend, self).unlink(fname)
533 self.removed.add(fname)
538 self.removed.add(fname)
534 self.changed.add(fname)
539 self.changed.add(fname)
535
540
536 def close(self):
541 def close(self):
537 wctx = self.repo[None]
542 wctx = self.repo[None]
538 changed = set(self.changed)
543 changed = set(self.changed)
539 for src, dst in self.copied:
544 for src, dst in self.copied:
540 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
545 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
541 if self.removed:
546 if self.removed:
542 wctx.forget(sorted(self.removed))
547 wctx.forget(sorted(self.removed))
543 for f in self.removed:
548 for f in self.removed:
544 if f not in self.repo.dirstate:
549 if f not in self.repo.dirstate:
545 # File was deleted and no longer belongs to the
550 # File was deleted and no longer belongs to the
546 # dirstate, it was probably marked added then
551 # dirstate, it was probably marked added then
547 # deleted, and should not be considered by
552 # deleted, and should not be considered by
548 # marktouched().
553 # marktouched().
549 changed.discard(f)
554 changed.discard(f)
550 if changed:
555 if changed:
551 scmutil.marktouched(self.repo, changed, self.similarity)
556 scmutil.marktouched(self.repo, changed, self.similarity)
552 return sorted(self.changed)
557 return sorted(self.changed)
553
558
554 class filestore(object):
559 class filestore(object):
555 def __init__(self, maxsize=None):
560 def __init__(self, maxsize=None):
556 self.opener = None
561 self.opener = None
557 self.files = {}
562 self.files = {}
558 self.created = 0
563 self.created = 0
559 self.maxsize = maxsize
564 self.maxsize = maxsize
560 if self.maxsize is None:
565 if self.maxsize is None:
561 self.maxsize = 4*(2**20)
566 self.maxsize = 4*(2**20)
562 self.size = 0
567 self.size = 0
563 self.data = {}
568 self.data = {}
564
569
565 def setfile(self, fname, data, mode, copied=None):
570 def setfile(self, fname, data, mode, copied=None):
566 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
571 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
567 self.data[fname] = (data, mode, copied)
572 self.data[fname] = (data, mode, copied)
568 self.size += len(data)
573 self.size += len(data)
569 else:
574 else:
570 if self.opener is None:
575 if self.opener is None:
571 root = tempfile.mkdtemp(prefix='hg-patch-')
576 root = tempfile.mkdtemp(prefix='hg-patch-')
572 self.opener = vfsmod.vfs(root)
577 self.opener = vfsmod.vfs(root)
573 # Avoid filename issues with these simple names
578 # Avoid filename issues with these simple names
574 fn = '%d' % self.created
579 fn = '%d' % self.created
575 self.opener.write(fn, data)
580 self.opener.write(fn, data)
576 self.created += 1
581 self.created += 1
577 self.files[fname] = (fn, mode, copied)
582 self.files[fname] = (fn, mode, copied)
578
583
579 def getfile(self, fname):
584 def getfile(self, fname):
580 if fname in self.data:
585 if fname in self.data:
581 return self.data[fname]
586 return self.data[fname]
582 if not self.opener or fname not in self.files:
587 if not self.opener or fname not in self.files:
583 return None, None, None
588 return None, None, None
584 fn, mode, copied = self.files[fname]
589 fn, mode, copied = self.files[fname]
585 return self.opener.read(fn), mode, copied
590 return self.opener.read(fn), mode, copied
586
591
587 def close(self):
592 def close(self):
588 if self.opener:
593 if self.opener:
589 shutil.rmtree(self.opener.base)
594 shutil.rmtree(self.opener.base)
590
595
591 class repobackend(abstractbackend):
596 class repobackend(abstractbackend):
592 def __init__(self, ui, repo, ctx, store):
597 def __init__(self, ui, repo, ctx, store):
593 super(repobackend, self).__init__(ui)
598 super(repobackend, self).__init__(ui)
594 self.repo = repo
599 self.repo = repo
595 self.ctx = ctx
600 self.ctx = ctx
596 self.store = store
601 self.store = store
597 self.changed = set()
602 self.changed = set()
598 self.removed = set()
603 self.removed = set()
599 self.copied = {}
604 self.copied = {}
600
605
601 def _checkknown(self, fname):
606 def _checkknown(self, fname):
602 if fname not in self.ctx:
607 if fname not in self.ctx:
603 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
608 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
604
609
605 def getfile(self, fname):
610 def getfile(self, fname):
606 try:
611 try:
607 fctx = self.ctx[fname]
612 fctx = self.ctx[fname]
608 except error.LookupError:
613 except error.LookupError:
609 return None, None
614 return None, None
610 flags = fctx.flags()
615 flags = fctx.flags()
611 return fctx.data(), ('l' in flags, 'x' in flags)
616 return fctx.data(), ('l' in flags, 'x' in flags)
612
617
613 def setfile(self, fname, data, mode, copysource):
618 def setfile(self, fname, data, mode, copysource):
614 if copysource:
619 if copysource:
615 self._checkknown(copysource)
620 self._checkknown(copysource)
616 if data is None:
621 if data is None:
617 data = self.ctx[fname].data()
622 data = self.ctx[fname].data()
618 self.store.setfile(fname, data, mode, copysource)
623 self.store.setfile(fname, data, mode, copysource)
619 self.changed.add(fname)
624 self.changed.add(fname)
620 if copysource:
625 if copysource:
621 self.copied[fname] = copysource
626 self.copied[fname] = copysource
622
627
623 def unlink(self, fname):
628 def unlink(self, fname):
624 self._checkknown(fname)
629 self._checkknown(fname)
625 self.removed.add(fname)
630 self.removed.add(fname)
626
631
627 def exists(self, fname):
632 def exists(self, fname):
628 return fname in self.ctx
633 return fname in self.ctx
629
634
630 def close(self):
635 def close(self):
631 return self.changed | self.removed
636 return self.changed | self.removed
632
637
633 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
638 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
634 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
639 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
635 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
640 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
636 eolmodes = ['strict', 'crlf', 'lf', 'auto']
641 eolmodes = ['strict', 'crlf', 'lf', 'auto']
637
642
638 class patchfile(object):
643 class patchfile(object):
639 def __init__(self, ui, gp, backend, store, eolmode='strict'):
644 def __init__(self, ui, gp, backend, store, eolmode='strict'):
640 self.fname = gp.path
645 self.fname = gp.path
641 self.eolmode = eolmode
646 self.eolmode = eolmode
642 self.eol = None
647 self.eol = None
643 self.backend = backend
648 self.backend = backend
644 self.ui = ui
649 self.ui = ui
645 self.lines = []
650 self.lines = []
646 self.exists = False
651 self.exists = False
647 self.missing = True
652 self.missing = True
648 self.mode = gp.mode
653 self.mode = gp.mode
649 self.copysource = gp.oldpath
654 self.copysource = gp.oldpath
650 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
655 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
651 self.remove = gp.op == 'DELETE'
656 self.remove = gp.op == 'DELETE'
652 if self.copysource is None:
657 if self.copysource is None:
653 data, mode = backend.getfile(self.fname)
658 data, mode = backend.getfile(self.fname)
654 else:
659 else:
655 data, mode = store.getfile(self.copysource)[:2]
660 data, mode = store.getfile(self.copysource)[:2]
656 if data is not None:
661 if data is not None:
657 self.exists = self.copysource is None or backend.exists(self.fname)
662 self.exists = self.copysource is None or backend.exists(self.fname)
658 self.missing = False
663 self.missing = False
659 if data:
664 if data:
660 self.lines = mdiff.splitnewlines(data)
665 self.lines = mdiff.splitnewlines(data)
661 if self.mode is None:
666 if self.mode is None:
662 self.mode = mode
667 self.mode = mode
663 if self.lines:
668 if self.lines:
664 # Normalize line endings
669 # Normalize line endings
665 if self.lines[0].endswith('\r\n'):
670 if self.lines[0].endswith('\r\n'):
666 self.eol = '\r\n'
671 self.eol = '\r\n'
667 elif self.lines[0].endswith('\n'):
672 elif self.lines[0].endswith('\n'):
668 self.eol = '\n'
673 self.eol = '\n'
669 if eolmode != 'strict':
674 if eolmode != 'strict':
670 nlines = []
675 nlines = []
671 for l in self.lines:
676 for l in self.lines:
672 if l.endswith('\r\n'):
677 if l.endswith('\r\n'):
673 l = l[:-2] + '\n'
678 l = l[:-2] + '\n'
674 nlines.append(l)
679 nlines.append(l)
675 self.lines = nlines
680 self.lines = nlines
676 else:
681 else:
677 if self.create:
682 if self.create:
678 self.missing = False
683 self.missing = False
679 if self.mode is None:
684 if self.mode is None:
680 self.mode = (False, False)
685 self.mode = (False, False)
681 if self.missing:
686 if self.missing:
682 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
687 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
683 self.ui.warn(_("(use '--prefix' to apply patch relative to the "
688 self.ui.warn(_("(use '--prefix' to apply patch relative to the "
684 "current directory)\n"))
689 "current directory)\n"))
685
690
686 self.hash = {}
691 self.hash = {}
687 self.dirty = 0
692 self.dirty = 0
688 self.offset = 0
693 self.offset = 0
689 self.skew = 0
694 self.skew = 0
690 self.rej = []
695 self.rej = []
691 self.fileprinted = False
696 self.fileprinted = False
692 self.printfile(False)
697 self.printfile(False)
693 self.hunks = 0
698 self.hunks = 0
694
699
695 def writelines(self, fname, lines, mode):
700 def writelines(self, fname, lines, mode):
696 if self.eolmode == 'auto':
701 if self.eolmode == 'auto':
697 eol = self.eol
702 eol = self.eol
698 elif self.eolmode == 'crlf':
703 elif self.eolmode == 'crlf':
699 eol = '\r\n'
704 eol = '\r\n'
700 else:
705 else:
701 eol = '\n'
706 eol = '\n'
702
707
703 if self.eolmode != 'strict' and eol and eol != '\n':
708 if self.eolmode != 'strict' and eol and eol != '\n':
704 rawlines = []
709 rawlines = []
705 for l in lines:
710 for l in lines:
706 if l and l[-1] == '\n':
711 if l and l[-1] == '\n':
707 l = l[:-1] + eol
712 l = l[:-1] + eol
708 rawlines.append(l)
713 rawlines.append(l)
709 lines = rawlines
714 lines = rawlines
710
715
711 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
716 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
712
717
713 def printfile(self, warn):
718 def printfile(self, warn):
714 if self.fileprinted:
719 if self.fileprinted:
715 return
720 return
716 if warn or self.ui.verbose:
721 if warn or self.ui.verbose:
717 self.fileprinted = True
722 self.fileprinted = True
718 s = _("patching file %s\n") % self.fname
723 s = _("patching file %s\n") % self.fname
719 if warn:
724 if warn:
720 self.ui.warn(s)
725 self.ui.warn(s)
721 else:
726 else:
722 self.ui.note(s)
727 self.ui.note(s)
723
728
724
729
725 def findlines(self, l, linenum):
730 def findlines(self, l, linenum):
726 # looks through the hash and finds candidate lines. The
731 # looks through the hash and finds candidate lines. The
727 # result is a list of line numbers sorted based on distance
732 # result is a list of line numbers sorted based on distance
728 # from linenum
733 # from linenum
729
734
730 cand = self.hash.get(l, [])
735 cand = self.hash.get(l, [])
731 if len(cand) > 1:
736 if len(cand) > 1:
732 # resort our list of potentials forward then back.
737 # resort our list of potentials forward then back.
733 cand.sort(key=lambda x: abs(x - linenum))
738 cand.sort(key=lambda x: abs(x - linenum))
734 return cand
739 return cand
735
740
736 def write_rej(self):
741 def write_rej(self):
737 # our rejects are a little different from patch(1). This always
742 # our rejects are a little different from patch(1). This always
738 # creates rejects in the same form as the original patch. A file
743 # creates rejects in the same form as the original patch. A file
739 # header is inserted so that you can run the reject through patch again
744 # header is inserted so that you can run the reject through patch again
740 # without having to type the filename.
745 # without having to type the filename.
741 if not self.rej:
746 if not self.rej:
742 return
747 return
743 base = os.path.basename(self.fname)
748 base = os.path.basename(self.fname)
744 lines = ["--- %s\n+++ %s\n" % (base, base)]
749 lines = ["--- %s\n+++ %s\n" % (base, base)]
745 for x in self.rej:
750 for x in self.rej:
746 for l in x.hunk:
751 for l in x.hunk:
747 lines.append(l)
752 lines.append(l)
748 if l[-1:] != '\n':
753 if l[-1:] != '\n':
749 lines.append("\n\ No newline at end of file\n")
754 lines.append("\n\ No newline at end of file\n")
750 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
755 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
751
756
752 def apply(self, h):
757 def apply(self, h):
753 if not h.complete():
758 if not h.complete():
754 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
759 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
755 (h.number, h.desc, len(h.a), h.lena, len(h.b),
760 (h.number, h.desc, len(h.a), h.lena, len(h.b),
756 h.lenb))
761 h.lenb))
757
762
758 self.hunks += 1
763 self.hunks += 1
759
764
760 if self.missing:
765 if self.missing:
761 self.rej.append(h)
766 self.rej.append(h)
762 return -1
767 return -1
763
768
764 if self.exists and self.create:
769 if self.exists and self.create:
765 if self.copysource:
770 if self.copysource:
766 self.ui.warn(_("cannot create %s: destination already "
771 self.ui.warn(_("cannot create %s: destination already "
767 "exists\n") % self.fname)
772 "exists\n") % self.fname)
768 else:
773 else:
769 self.ui.warn(_("file %s already exists\n") % self.fname)
774 self.ui.warn(_("file %s already exists\n") % self.fname)
770 self.rej.append(h)
775 self.rej.append(h)
771 return -1
776 return -1
772
777
773 if isinstance(h, binhunk):
778 if isinstance(h, binhunk):
774 if self.remove:
779 if self.remove:
775 self.backend.unlink(self.fname)
780 self.backend.unlink(self.fname)
776 else:
781 else:
777 l = h.new(self.lines)
782 l = h.new(self.lines)
778 self.lines[:] = l
783 self.lines[:] = l
779 self.offset += len(l)
784 self.offset += len(l)
780 self.dirty = True
785 self.dirty = True
781 return 0
786 return 0
782
787
783 horig = h
788 horig = h
784 if (self.eolmode in ('crlf', 'lf')
789 if (self.eolmode in ('crlf', 'lf')
785 or self.eolmode == 'auto' and self.eol):
790 or self.eolmode == 'auto' and self.eol):
786 # If new eols are going to be normalized, then normalize
791 # If new eols are going to be normalized, then normalize
787 # hunk data before patching. Otherwise, preserve input
792 # hunk data before patching. Otherwise, preserve input
788 # line-endings.
793 # line-endings.
789 h = h.getnormalized()
794 h = h.getnormalized()
790
795
791 # fast case first, no offsets, no fuzz
796 # fast case first, no offsets, no fuzz
792 old, oldstart, new, newstart = h.fuzzit(0, False)
797 old, oldstart, new, newstart = h.fuzzit(0, False)
793 oldstart += self.offset
798 oldstart += self.offset
794 orig_start = oldstart
799 orig_start = oldstart
795 # if there's skew we want to emit the "(offset %d lines)" even
800 # if there's skew we want to emit the "(offset %d lines)" even
796 # when the hunk cleanly applies at start + skew, so skip the
801 # when the hunk cleanly applies at start + skew, so skip the
797 # fast case code
802 # fast case code
798 if self.skew == 0 and diffhelpers.testhunk(old, self.lines, oldstart):
803 if self.skew == 0 and diffhelpers.testhunk(old, self.lines, oldstart):
799 if self.remove:
804 if self.remove:
800 self.backend.unlink(self.fname)
805 self.backend.unlink(self.fname)
801 else:
806 else:
802 self.lines[oldstart:oldstart + len(old)] = new
807 self.lines[oldstart:oldstart + len(old)] = new
803 self.offset += len(new) - len(old)
808 self.offset += len(new) - len(old)
804 self.dirty = True
809 self.dirty = True
805 return 0
810 return 0
806
811
807 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
812 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
808 self.hash = {}
813 self.hash = {}
809 for x, s in enumerate(self.lines):
814 for x, s in enumerate(self.lines):
810 self.hash.setdefault(s, []).append(x)
815 self.hash.setdefault(s, []).append(x)
811
816
812 for fuzzlen in xrange(self.ui.configint("patch", "fuzz") + 1):
817 for fuzzlen in xrange(self.ui.configint("patch", "fuzz") + 1):
813 for toponly in [True, False]:
818 for toponly in [True, False]:
814 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
819 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
815 oldstart = oldstart + self.offset + self.skew
820 oldstart = oldstart + self.offset + self.skew
816 oldstart = min(oldstart, len(self.lines))
821 oldstart = min(oldstart, len(self.lines))
817 if old:
822 if old:
818 cand = self.findlines(old[0][1:], oldstart)
823 cand = self.findlines(old[0][1:], oldstart)
819 else:
824 else:
820 # Only adding lines with no or fuzzed context, just
825 # Only adding lines with no or fuzzed context, just
821 # take the skew in account
826 # take the skew in account
822 cand = [oldstart]
827 cand = [oldstart]
823
828
824 for l in cand:
829 for l in cand:
825 if not old or diffhelpers.testhunk(old, self.lines, l):
830 if not old or diffhelpers.testhunk(old, self.lines, l):
826 self.lines[l : l + len(old)] = new
831 self.lines[l : l + len(old)] = new
827 self.offset += len(new) - len(old)
832 self.offset += len(new) - len(old)
828 self.skew = l - orig_start
833 self.skew = l - orig_start
829 self.dirty = True
834 self.dirty = True
830 offset = l - orig_start - fuzzlen
835 offset = l - orig_start - fuzzlen
831 if fuzzlen:
836 if fuzzlen:
832 msg = _("Hunk #%d succeeded at %d "
837 msg = _("Hunk #%d succeeded at %d "
833 "with fuzz %d "
838 "with fuzz %d "
834 "(offset %d lines).\n")
839 "(offset %d lines).\n")
835 self.printfile(True)
840 self.printfile(True)
836 self.ui.warn(msg %
841 self.ui.warn(msg %
837 (h.number, l + 1, fuzzlen, offset))
842 (h.number, l + 1, fuzzlen, offset))
838 else:
843 else:
839 msg = _("Hunk #%d succeeded at %d "
844 msg = _("Hunk #%d succeeded at %d "
840 "(offset %d lines).\n")
845 "(offset %d lines).\n")
841 self.ui.note(msg % (h.number, l + 1, offset))
846 self.ui.note(msg % (h.number, l + 1, offset))
842 return fuzzlen
847 return fuzzlen
843 self.printfile(True)
848 self.printfile(True)
844 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
849 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
845 self.rej.append(horig)
850 self.rej.append(horig)
846 return -1
851 return -1
847
852
848 def close(self):
853 def close(self):
849 if self.dirty:
854 if self.dirty:
850 self.writelines(self.fname, self.lines, self.mode)
855 self.writelines(self.fname, self.lines, self.mode)
851 self.write_rej()
856 self.write_rej()
852 return len(self.rej)
857 return len(self.rej)
853
858
854 class header(object):
859 class header(object):
855 """patch header
860 """patch header
856 """
861 """
857 diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
862 diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
858 diff_re = re.compile('diff -r .* (.*)$')
863 diff_re = re.compile('diff -r .* (.*)$')
859 allhunks_re = re.compile('(?:index|deleted file) ')
864 allhunks_re = re.compile('(?:index|deleted file) ')
860 pretty_re = re.compile('(?:new file|deleted file) ')
865 pretty_re = re.compile('(?:new file|deleted file) ')
861 special_re = re.compile('(?:index|deleted|copy|rename) ')
866 special_re = re.compile('(?:index|deleted|copy|rename) ')
862 newfile_re = re.compile('(?:new file)')
867 newfile_re = re.compile('(?:new file)')
863
868
864 def __init__(self, header):
869 def __init__(self, header):
865 self.header = header
870 self.header = header
866 self.hunks = []
871 self.hunks = []
867
872
868 def binary(self):
873 def binary(self):
869 return any(h.startswith('index ') for h in self.header)
874 return any(h.startswith('index ') for h in self.header)
870
875
871 def pretty(self, fp):
876 def pretty(self, fp):
872 for h in self.header:
877 for h in self.header:
873 if h.startswith('index '):
878 if h.startswith('index '):
874 fp.write(_('this modifies a binary file (all or nothing)\n'))
879 fp.write(_('this modifies a binary file (all or nothing)\n'))
875 break
880 break
876 if self.pretty_re.match(h):
881 if self.pretty_re.match(h):
877 fp.write(h)
882 fp.write(h)
878 if self.binary():
883 if self.binary():
879 fp.write(_('this is a binary file\n'))
884 fp.write(_('this is a binary file\n'))
880 break
885 break
881 if h.startswith('---'):
886 if h.startswith('---'):
882 fp.write(_('%d hunks, %d lines changed\n') %
887 fp.write(_('%d hunks, %d lines changed\n') %
883 (len(self.hunks),
888 (len(self.hunks),
884 sum([max(h.added, h.removed) for h in self.hunks])))
889 sum([max(h.added, h.removed) for h in self.hunks])))
885 break
890 break
886 fp.write(h)
891 fp.write(h)
887
892
888 def write(self, fp):
893 def write(self, fp):
889 fp.write(''.join(self.header))
894 fp.write(''.join(self.header))
890
895
891 def allhunks(self):
896 def allhunks(self):
892 return any(self.allhunks_re.match(h) for h in self.header)
897 return any(self.allhunks_re.match(h) for h in self.header)
893
898
894 def files(self):
899 def files(self):
895 match = self.diffgit_re.match(self.header[0])
900 match = self.diffgit_re.match(self.header[0])
896 if match:
901 if match:
897 fromfile, tofile = match.groups()
902 fromfile, tofile = match.groups()
898 if fromfile == tofile:
903 if fromfile == tofile:
899 return [fromfile]
904 return [fromfile]
900 return [fromfile, tofile]
905 return [fromfile, tofile]
901 else:
906 else:
902 return self.diff_re.match(self.header[0]).groups()
907 return self.diff_re.match(self.header[0]).groups()
903
908
904 def filename(self):
909 def filename(self):
905 return self.files()[-1]
910 return self.files()[-1]
906
911
907 def __repr__(self):
912 def __repr__(self):
908 return '<header %s>' % (' '.join(map(repr, self.files())))
913 return '<header %s>' % (' '.join(map(repr, self.files())))
909
914
910 def isnewfile(self):
915 def isnewfile(self):
911 return any(self.newfile_re.match(h) for h in self.header)
916 return any(self.newfile_re.match(h) for h in self.header)
912
917
913 def special(self):
918 def special(self):
914 # Special files are shown only at the header level and not at the hunk
919 # Special files are shown only at the header level and not at the hunk
915 # level for example a file that has been deleted is a special file.
920 # level for example a file that has been deleted is a special file.
916 # The user cannot change the content of the operation, in the case of
921 # The user cannot change the content of the operation, in the case of
917 # the deleted file he has to take the deletion or not take it, he
922 # the deleted file he has to take the deletion or not take it, he
918 # cannot take some of it.
923 # cannot take some of it.
919 # Newly added files are special if they are empty, they are not special
924 # Newly added files are special if they are empty, they are not special
920 # if they have some content as we want to be able to change it
925 # if they have some content as we want to be able to change it
921 nocontent = len(self.header) == 2
926 nocontent = len(self.header) == 2
922 emptynewfile = self.isnewfile() and nocontent
927 emptynewfile = self.isnewfile() and nocontent
923 return emptynewfile or \
928 return emptynewfile or \
924 any(self.special_re.match(h) for h in self.header)
929 any(self.special_re.match(h) for h in self.header)
925
930
926 class recordhunk(object):
931 class recordhunk(object):
927 """patch hunk
932 """patch hunk
928
933
929 XXX shouldn't we merge this with the other hunk class?
934 XXX shouldn't we merge this with the other hunk class?
930 """
935 """
931
936
932 def __init__(self, header, fromline, toline, proc, before, hunk, after,
937 def __init__(self, header, fromline, toline, proc, before, hunk, after,
933 maxcontext=None):
938 maxcontext=None):
934 def trimcontext(lines, reverse=False):
939 def trimcontext(lines, reverse=False):
935 if maxcontext is not None:
940 if maxcontext is not None:
936 delta = len(lines) - maxcontext
941 delta = len(lines) - maxcontext
937 if delta > 0:
942 if delta > 0:
938 if reverse:
943 if reverse:
939 return delta, lines[delta:]
944 return delta, lines[delta:]
940 else:
945 else:
941 return delta, lines[:maxcontext]
946 return delta, lines[:maxcontext]
942 return 0, lines
947 return 0, lines
943
948
944 self.header = header
949 self.header = header
945 trimedbefore, self.before = trimcontext(before, True)
950 trimedbefore, self.before = trimcontext(before, True)
946 self.fromline = fromline + trimedbefore
951 self.fromline = fromline + trimedbefore
947 self.toline = toline + trimedbefore
952 self.toline = toline + trimedbefore
948 _trimedafter, self.after = trimcontext(after, False)
953 _trimedafter, self.after = trimcontext(after, False)
949 self.proc = proc
954 self.proc = proc
950 self.hunk = hunk
955 self.hunk = hunk
951 self.added, self.removed = self.countchanges(self.hunk)
956 self.added, self.removed = self.countchanges(self.hunk)
952
957
953 def __eq__(self, v):
958 def __eq__(self, v):
954 if not isinstance(v, recordhunk):
959 if not isinstance(v, recordhunk):
955 return False
960 return False
956
961
957 return ((v.hunk == self.hunk) and
962 return ((v.hunk == self.hunk) and
958 (v.proc == self.proc) and
963 (v.proc == self.proc) and
959 (self.fromline == v.fromline) and
964 (self.fromline == v.fromline) and
960 (self.header.files() == v.header.files()))
965 (self.header.files() == v.header.files()))
961
966
962 def __hash__(self):
967 def __hash__(self):
963 return hash((tuple(self.hunk),
968 return hash((tuple(self.hunk),
964 tuple(self.header.files()),
969 tuple(self.header.files()),
965 self.fromline,
970 self.fromline,
966 self.proc))
971 self.proc))
967
972
968 def countchanges(self, hunk):
973 def countchanges(self, hunk):
969 """hunk -> (n+,n-)"""
974 """hunk -> (n+,n-)"""
970 add = len([h for h in hunk if h.startswith('+')])
975 add = len([h for h in hunk if h.startswith('+')])
971 rem = len([h for h in hunk if h.startswith('-')])
976 rem = len([h for h in hunk if h.startswith('-')])
972 return add, rem
977 return add, rem
973
978
974 def reversehunk(self):
979 def reversehunk(self):
975 """return another recordhunk which is the reverse of the hunk
980 """return another recordhunk which is the reverse of the hunk
976
981
977 If this hunk is diff(A, B), the returned hunk is diff(B, A). To do
982 If this hunk is diff(A, B), the returned hunk is diff(B, A). To do
978 that, swap fromline/toline and +/- signs while keep other things
983 that, swap fromline/toline and +/- signs while keep other things
979 unchanged.
984 unchanged.
980 """
985 """
981 m = {'+': '-', '-': '+', '\\': '\\'}
986 m = {'+': '-', '-': '+', '\\': '\\'}
982 hunk = ['%s%s' % (m[l[0:1]], l[1:]) for l in self.hunk]
987 hunk = ['%s%s' % (m[l[0:1]], l[1:]) for l in self.hunk]
983 return recordhunk(self.header, self.toline, self.fromline, self.proc,
988 return recordhunk(self.header, self.toline, self.fromline, self.proc,
984 self.before, hunk, self.after)
989 self.before, hunk, self.after)
985
990
986 def write(self, fp):
991 def write(self, fp):
987 delta = len(self.before) + len(self.after)
992 delta = len(self.before) + len(self.after)
988 if self.after and self.after[-1] == '\\ No newline at end of file\n':
993 if self.after and self.after[-1] == '\\ No newline at end of file\n':
989 delta -= 1
994 delta -= 1
990 fromlen = delta + self.removed
995 fromlen = delta + self.removed
991 tolen = delta + self.added
996 tolen = delta + self.added
992 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
997 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
993 (self.fromline, fromlen, self.toline, tolen,
998 (self.fromline, fromlen, self.toline, tolen,
994 self.proc and (' ' + self.proc)))
999 self.proc and (' ' + self.proc)))
995 fp.write(''.join(self.before + self.hunk + self.after))
1000 fp.write(''.join(self.before + self.hunk + self.after))
996
1001
997 pretty = write
1002 pretty = write
998
1003
999 def filename(self):
1004 def filename(self):
1000 return self.header.filename()
1005 return self.header.filename()
1001
1006
1002 def __repr__(self):
1007 def __repr__(self):
1003 return '<hunk %r@%d>' % (self.filename(), self.fromline)
1008 return '<hunk %r@%d>' % (self.filename(), self.fromline)
1004
1009
1005 def getmessages():
1010 def getmessages():
1006 return {
1011 return {
1007 'multiple': {
1012 'multiple': {
1008 'apply': _("apply change %d/%d to '%s'?"),
1013 'apply': _("apply change %d/%d to '%s'?"),
1009 'discard': _("discard change %d/%d to '%s'?"),
1014 'discard': _("discard change %d/%d to '%s'?"),
1010 'record': _("record change %d/%d to '%s'?"),
1015 'record': _("record change %d/%d to '%s'?"),
1011 },
1016 },
1012 'single': {
1017 'single': {
1013 'apply': _("apply this change to '%s'?"),
1018 'apply': _("apply this change to '%s'?"),
1014 'discard': _("discard this change to '%s'?"),
1019 'discard': _("discard this change to '%s'?"),
1015 'record': _("record this change to '%s'?"),
1020 'record': _("record this change to '%s'?"),
1016 },
1021 },
1017 'help': {
1022 'help': {
1018 'apply': _('[Ynesfdaq?]'
1023 'apply': _('[Ynesfdaq?]'
1019 '$$ &Yes, apply this change'
1024 '$$ &Yes, apply this change'
1020 '$$ &No, skip this change'
1025 '$$ &No, skip this change'
1021 '$$ &Edit this change manually'
1026 '$$ &Edit this change manually'
1022 '$$ &Skip remaining changes to this file'
1027 '$$ &Skip remaining changes to this file'
1023 '$$ Apply remaining changes to this &file'
1028 '$$ Apply remaining changes to this &file'
1024 '$$ &Done, skip remaining changes and files'
1029 '$$ &Done, skip remaining changes and files'
1025 '$$ Apply &all changes to all remaining files'
1030 '$$ Apply &all changes to all remaining files'
1026 '$$ &Quit, applying no changes'
1031 '$$ &Quit, applying no changes'
1027 '$$ &? (display help)'),
1032 '$$ &? (display help)'),
1028 'discard': _('[Ynesfdaq?]'
1033 'discard': _('[Ynesfdaq?]'
1029 '$$ &Yes, discard this change'
1034 '$$ &Yes, discard this change'
1030 '$$ &No, skip this change'
1035 '$$ &No, skip this change'
1031 '$$ &Edit this change manually'
1036 '$$ &Edit this change manually'
1032 '$$ &Skip remaining changes to this file'
1037 '$$ &Skip remaining changes to this file'
1033 '$$ Discard remaining changes to this &file'
1038 '$$ Discard remaining changes to this &file'
1034 '$$ &Done, skip remaining changes and files'
1039 '$$ &Done, skip remaining changes and files'
1035 '$$ Discard &all changes to all remaining files'
1040 '$$ Discard &all changes to all remaining files'
1036 '$$ &Quit, discarding no changes'
1041 '$$ &Quit, discarding no changes'
1037 '$$ &? (display help)'),
1042 '$$ &? (display help)'),
1038 'record': _('[Ynesfdaq?]'
1043 'record': _('[Ynesfdaq?]'
1039 '$$ &Yes, record this change'
1044 '$$ &Yes, record this change'
1040 '$$ &No, skip this change'
1045 '$$ &No, skip this change'
1041 '$$ &Edit this change manually'
1046 '$$ &Edit this change manually'
1042 '$$ &Skip remaining changes to this file'
1047 '$$ &Skip remaining changes to this file'
1043 '$$ Record remaining changes to this &file'
1048 '$$ Record remaining changes to this &file'
1044 '$$ &Done, skip remaining changes and files'
1049 '$$ &Done, skip remaining changes and files'
1045 '$$ Record &all changes to all remaining files'
1050 '$$ Record &all changes to all remaining files'
1046 '$$ &Quit, recording no changes'
1051 '$$ &Quit, recording no changes'
1047 '$$ &? (display help)'),
1052 '$$ &? (display help)'),
1048 }
1053 }
1049 }
1054 }
1050
1055
1051 def filterpatch(ui, headers, operation=None):
1056 def filterpatch(ui, headers, operation=None):
1052 """Interactively filter patch chunks into applied-only chunks"""
1057 """Interactively filter patch chunks into applied-only chunks"""
1053 messages = getmessages()
1058 messages = getmessages()
1054
1059
1055 if operation is None:
1060 if operation is None:
1056 operation = 'record'
1061 operation = 'record'
1057
1062
1058 def prompt(skipfile, skipall, query, chunk):
1063 def prompt(skipfile, skipall, query, chunk):
1059 """prompt query, and process base inputs
1064 """prompt query, and process base inputs
1060
1065
1061 - y/n for the rest of file
1066 - y/n for the rest of file
1062 - y/n for the rest
1067 - y/n for the rest
1063 - ? (help)
1068 - ? (help)
1064 - q (quit)
1069 - q (quit)
1065
1070
1066 Return True/False and possibly updated skipfile and skipall.
1071 Return True/False and possibly updated skipfile and skipall.
1067 """
1072 """
1068 newpatches = None
1073 newpatches = None
1069 if skipall is not None:
1074 if skipall is not None:
1070 return skipall, skipfile, skipall, newpatches
1075 return skipall, skipfile, skipall, newpatches
1071 if skipfile is not None:
1076 if skipfile is not None:
1072 return skipfile, skipfile, skipall, newpatches
1077 return skipfile, skipfile, skipall, newpatches
1073 while True:
1078 while True:
1074 resps = messages['help'][operation]
1079 resps = messages['help'][operation]
1075 r = ui.promptchoice("%s %s" % (query, resps))
1080 r = ui.promptchoice("%s %s" % (query, resps))
1076 ui.write("\n")
1081 ui.write("\n")
1077 if r == 8: # ?
1082 if r == 8: # ?
1078 for c, t in ui.extractchoices(resps)[1]:
1083 for c, t in ui.extractchoices(resps)[1]:
1079 ui.write('%s - %s\n' % (c, encoding.lower(t)))
1084 ui.write('%s - %s\n' % (c, encoding.lower(t)))
1080 continue
1085 continue
1081 elif r == 0: # yes
1086 elif r == 0: # yes
1082 ret = True
1087 ret = True
1083 elif r == 1: # no
1088 elif r == 1: # no
1084 ret = False
1089 ret = False
1085 elif r == 2: # Edit patch
1090 elif r == 2: # Edit patch
1086 if chunk is None:
1091 if chunk is None:
1087 ui.write(_('cannot edit patch for whole file'))
1092 ui.write(_('cannot edit patch for whole file'))
1088 ui.write("\n")
1093 ui.write("\n")
1089 continue
1094 continue
1090 if chunk.header.binary():
1095 if chunk.header.binary():
1091 ui.write(_('cannot edit patch for binary file'))
1096 ui.write(_('cannot edit patch for binary file'))
1092 ui.write("\n")
1097 ui.write("\n")
1093 continue
1098 continue
1094 # Patch comment based on the Git one (based on comment at end of
1099 # Patch comment based on the Git one (based on comment at end of
1095 # https://mercurial-scm.org/wiki/RecordExtension)
1100 # https://mercurial-scm.org/wiki/RecordExtension)
1096 phelp = '---' + _("""
1101 phelp = '---' + _("""
1097 To remove '-' lines, make them ' ' lines (context).
1102 To remove '-' lines, make them ' ' lines (context).
1098 To remove '+' lines, delete them.
1103 To remove '+' lines, delete them.
1099 Lines starting with # will be removed from the patch.
1104 Lines starting with # will be removed from the patch.
1100
1105
1101 If the patch applies cleanly, the edited hunk will immediately be
1106 If the patch applies cleanly, the edited hunk will immediately be
1102 added to the record list. If it does not apply cleanly, a rejects
1107 added to the record list. If it does not apply cleanly, a rejects
1103 file will be generated: you can use that when you try again. If
1108 file will be generated: you can use that when you try again. If
1104 all lines of the hunk are removed, then the edit is aborted and
1109 all lines of the hunk are removed, then the edit is aborted and
1105 the hunk is left unchanged.
1110 the hunk is left unchanged.
1106 """)
1111 """)
1107 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
1112 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
1108 suffix=".diff")
1113 suffix=".diff")
1109 ncpatchfp = None
1114 ncpatchfp = None
1110 try:
1115 try:
1111 # Write the initial patch
1116 # Write the initial patch
1112 f = util.nativeeolwriter(os.fdopen(patchfd, r'wb'))
1117 f = util.nativeeolwriter(os.fdopen(patchfd, r'wb'))
1113 chunk.header.write(f)
1118 chunk.header.write(f)
1114 chunk.write(f)
1119 chunk.write(f)
1115 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
1120 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
1116 f.close()
1121 f.close()
1117 # Start the editor and wait for it to complete
1122 # Start the editor and wait for it to complete
1118 editor = ui.geteditor()
1123 editor = ui.geteditor()
1119 ret = ui.system("%s \"%s\"" % (editor, patchfn),
1124 ret = ui.system("%s \"%s\"" % (editor, patchfn),
1120 environ={'HGUSER': ui.username()},
1125 environ={'HGUSER': ui.username()},
1121 blockedtag='filterpatch')
1126 blockedtag='filterpatch')
1122 if ret != 0:
1127 if ret != 0:
1123 ui.warn(_("editor exited with exit code %d\n") % ret)
1128 ui.warn(_("editor exited with exit code %d\n") % ret)
1124 continue
1129 continue
1125 # Remove comment lines
1130 # Remove comment lines
1126 patchfp = open(patchfn, r'rb')
1131 patchfp = open(patchfn, r'rb')
1127 ncpatchfp = stringio()
1132 ncpatchfp = stringio()
1128 for line in util.iterfile(patchfp):
1133 for line in util.iterfile(patchfp):
1129 line = util.fromnativeeol(line)
1134 line = util.fromnativeeol(line)
1130 if not line.startswith('#'):
1135 if not line.startswith('#'):
1131 ncpatchfp.write(line)
1136 ncpatchfp.write(line)
1132 patchfp.close()
1137 patchfp.close()
1133 ncpatchfp.seek(0)
1138 ncpatchfp.seek(0)
1134 newpatches = parsepatch(ncpatchfp)
1139 newpatches = parsepatch(ncpatchfp)
1135 finally:
1140 finally:
1136 os.unlink(patchfn)
1141 os.unlink(patchfn)
1137 del ncpatchfp
1142 del ncpatchfp
1138 # Signal that the chunk shouldn't be applied as-is, but
1143 # Signal that the chunk shouldn't be applied as-is, but
1139 # provide the new patch to be used instead.
1144 # provide the new patch to be used instead.
1140 ret = False
1145 ret = False
1141 elif r == 3: # Skip
1146 elif r == 3: # Skip
1142 ret = skipfile = False
1147 ret = skipfile = False
1143 elif r == 4: # file (Record remaining)
1148 elif r == 4: # file (Record remaining)
1144 ret = skipfile = True
1149 ret = skipfile = True
1145 elif r == 5: # done, skip remaining
1150 elif r == 5: # done, skip remaining
1146 ret = skipall = False
1151 ret = skipall = False
1147 elif r == 6: # all
1152 elif r == 6: # all
1148 ret = skipall = True
1153 ret = skipall = True
1149 elif r == 7: # quit
1154 elif r == 7: # quit
1150 raise error.Abort(_('user quit'))
1155 raise error.Abort(_('user quit'))
1151 return ret, skipfile, skipall, newpatches
1156 return ret, skipfile, skipall, newpatches
1152
1157
1153 seen = set()
1158 seen = set()
1154 applied = {} # 'filename' -> [] of chunks
1159 applied = {} # 'filename' -> [] of chunks
1155 skipfile, skipall = None, None
1160 skipfile, skipall = None, None
1156 pos, total = 1, sum(len(h.hunks) for h in headers)
1161 pos, total = 1, sum(len(h.hunks) for h in headers)
1157 for h in headers:
1162 for h in headers:
1158 pos += len(h.hunks)
1163 pos += len(h.hunks)
1159 skipfile = None
1164 skipfile = None
1160 fixoffset = 0
1165 fixoffset = 0
1161 hdr = ''.join(h.header)
1166 hdr = ''.join(h.header)
1162 if hdr in seen:
1167 if hdr in seen:
1163 continue
1168 continue
1164 seen.add(hdr)
1169 seen.add(hdr)
1165 if skipall is None:
1170 if skipall is None:
1166 h.pretty(ui)
1171 h.pretty(ui)
1167 msg = (_('examine changes to %s?') %
1172 msg = (_('examine changes to %s?') %
1168 _(' and ').join("'%s'" % f for f in h.files()))
1173 _(' and ').join("'%s'" % f for f in h.files()))
1169 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
1174 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
1170 if not r:
1175 if not r:
1171 continue
1176 continue
1172 applied[h.filename()] = [h]
1177 applied[h.filename()] = [h]
1173 if h.allhunks():
1178 if h.allhunks():
1174 applied[h.filename()] += h.hunks
1179 applied[h.filename()] += h.hunks
1175 continue
1180 continue
1176 for i, chunk in enumerate(h.hunks):
1181 for i, chunk in enumerate(h.hunks):
1177 if skipfile is None and skipall is None:
1182 if skipfile is None and skipall is None:
1178 chunk.pretty(ui)
1183 chunk.pretty(ui)
1179 if total == 1:
1184 if total == 1:
1180 msg = messages['single'][operation] % chunk.filename()
1185 msg = messages['single'][operation] % chunk.filename()
1181 else:
1186 else:
1182 idx = pos - len(h.hunks) + i
1187 idx = pos - len(h.hunks) + i
1183 msg = messages['multiple'][operation] % (idx, total,
1188 msg = messages['multiple'][operation] % (idx, total,
1184 chunk.filename())
1189 chunk.filename())
1185 r, skipfile, skipall, newpatches = prompt(skipfile,
1190 r, skipfile, skipall, newpatches = prompt(skipfile,
1186 skipall, msg, chunk)
1191 skipall, msg, chunk)
1187 if r:
1192 if r:
1188 if fixoffset:
1193 if fixoffset:
1189 chunk = copy.copy(chunk)
1194 chunk = copy.copy(chunk)
1190 chunk.toline += fixoffset
1195 chunk.toline += fixoffset
1191 applied[chunk.filename()].append(chunk)
1196 applied[chunk.filename()].append(chunk)
1192 elif newpatches is not None:
1197 elif newpatches is not None:
1193 for newpatch in newpatches:
1198 for newpatch in newpatches:
1194 for newhunk in newpatch.hunks:
1199 for newhunk in newpatch.hunks:
1195 if fixoffset:
1200 if fixoffset:
1196 newhunk.toline += fixoffset
1201 newhunk.toline += fixoffset
1197 applied[newhunk.filename()].append(newhunk)
1202 applied[newhunk.filename()].append(newhunk)
1198 else:
1203 else:
1199 fixoffset += chunk.removed - chunk.added
1204 fixoffset += chunk.removed - chunk.added
1200 return (sum([h for h in applied.itervalues()
1205 return (sum([h for h in applied.itervalues()
1201 if h[0].special() or len(h) > 1], []), {})
1206 if h[0].special() or len(h) > 1], []), {})
1202 class hunk(object):
1207 class hunk(object):
1203 def __init__(self, desc, num, lr, context):
1208 def __init__(self, desc, num, lr, context):
1204 self.number = num
1209 self.number = num
1205 self.desc = desc
1210 self.desc = desc
1206 self.hunk = [desc]
1211 self.hunk = [desc]
1207 self.a = []
1212 self.a = []
1208 self.b = []
1213 self.b = []
1209 self.starta = self.lena = None
1214 self.starta = self.lena = None
1210 self.startb = self.lenb = None
1215 self.startb = self.lenb = None
1211 if lr is not None:
1216 if lr is not None:
1212 if context:
1217 if context:
1213 self.read_context_hunk(lr)
1218 self.read_context_hunk(lr)
1214 else:
1219 else:
1215 self.read_unified_hunk(lr)
1220 self.read_unified_hunk(lr)
1216
1221
1217 def getnormalized(self):
1222 def getnormalized(self):
1218 """Return a copy with line endings normalized to LF."""
1223 """Return a copy with line endings normalized to LF."""
1219
1224
1220 def normalize(lines):
1225 def normalize(lines):
1221 nlines = []
1226 nlines = []
1222 for line in lines:
1227 for line in lines:
1223 if line.endswith('\r\n'):
1228 if line.endswith('\r\n'):
1224 line = line[:-2] + '\n'
1229 line = line[:-2] + '\n'
1225 nlines.append(line)
1230 nlines.append(line)
1226 return nlines
1231 return nlines
1227
1232
1228 # Dummy object, it is rebuilt manually
1233 # Dummy object, it is rebuilt manually
1229 nh = hunk(self.desc, self.number, None, None)
1234 nh = hunk(self.desc, self.number, None, None)
1230 nh.number = self.number
1235 nh.number = self.number
1231 nh.desc = self.desc
1236 nh.desc = self.desc
1232 nh.hunk = self.hunk
1237 nh.hunk = self.hunk
1233 nh.a = normalize(self.a)
1238 nh.a = normalize(self.a)
1234 nh.b = normalize(self.b)
1239 nh.b = normalize(self.b)
1235 nh.starta = self.starta
1240 nh.starta = self.starta
1236 nh.startb = self.startb
1241 nh.startb = self.startb
1237 nh.lena = self.lena
1242 nh.lena = self.lena
1238 nh.lenb = self.lenb
1243 nh.lenb = self.lenb
1239 return nh
1244 return nh
1240
1245
1241 def read_unified_hunk(self, lr):
1246 def read_unified_hunk(self, lr):
1242 m = unidesc.match(self.desc)
1247 m = unidesc.match(self.desc)
1243 if not m:
1248 if not m:
1244 raise PatchError(_("bad hunk #%d") % self.number)
1249 raise PatchError(_("bad hunk #%d") % self.number)
1245 self.starta, self.lena, self.startb, self.lenb = m.groups()
1250 self.starta, self.lena, self.startb, self.lenb = m.groups()
1246 if self.lena is None:
1251 if self.lena is None:
1247 self.lena = 1
1252 self.lena = 1
1248 else:
1253 else:
1249 self.lena = int(self.lena)
1254 self.lena = int(self.lena)
1250 if self.lenb is None:
1255 if self.lenb is None:
1251 self.lenb = 1
1256 self.lenb = 1
1252 else:
1257 else:
1253 self.lenb = int(self.lenb)
1258 self.lenb = int(self.lenb)
1254 self.starta = int(self.starta)
1259 self.starta = int(self.starta)
1255 self.startb = int(self.startb)
1260 self.startb = int(self.startb)
1256 try:
1261 try:
1257 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb,
1262 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb,
1258 self.a, self.b)
1263 self.a, self.b)
1259 except error.ParseError as e:
1264 except error.ParseError as e:
1260 raise PatchError(_("bad hunk #%d: %s") % (self.number, e))
1265 raise PatchError(_("bad hunk #%d: %s") % (self.number, e))
1261 # if we hit eof before finishing out the hunk, the last line will
1266 # if we hit eof before finishing out the hunk, the last line will
1262 # be zero length. Lets try to fix it up.
1267 # be zero length. Lets try to fix it up.
1263 while len(self.hunk[-1]) == 0:
1268 while len(self.hunk[-1]) == 0:
1264 del self.hunk[-1]
1269 del self.hunk[-1]
1265 del self.a[-1]
1270 del self.a[-1]
1266 del self.b[-1]
1271 del self.b[-1]
1267 self.lena -= 1
1272 self.lena -= 1
1268 self.lenb -= 1
1273 self.lenb -= 1
1269 self._fixnewline(lr)
1274 self._fixnewline(lr)
1270
1275
1271 def read_context_hunk(self, lr):
1276 def read_context_hunk(self, lr):
1272 self.desc = lr.readline()
1277 self.desc = lr.readline()
1273 m = contextdesc.match(self.desc)
1278 m = contextdesc.match(self.desc)
1274 if not m:
1279 if not m:
1275 raise PatchError(_("bad hunk #%d") % self.number)
1280 raise PatchError(_("bad hunk #%d") % self.number)
1276 self.starta, aend = m.groups()
1281 self.starta, aend = m.groups()
1277 self.starta = int(self.starta)
1282 self.starta = int(self.starta)
1278 if aend is None:
1283 if aend is None:
1279 aend = self.starta
1284 aend = self.starta
1280 self.lena = int(aend) - self.starta
1285 self.lena = int(aend) - self.starta
1281 if self.starta:
1286 if self.starta:
1282 self.lena += 1
1287 self.lena += 1
1283 for x in xrange(self.lena):
1288 for x in xrange(self.lena):
1284 l = lr.readline()
1289 l = lr.readline()
1285 if l.startswith('---'):
1290 if l.startswith('---'):
1286 # lines addition, old block is empty
1291 # lines addition, old block is empty
1287 lr.push(l)
1292 lr.push(l)
1288 break
1293 break
1289 s = l[2:]
1294 s = l[2:]
1290 if l.startswith('- ') or l.startswith('! '):
1295 if l.startswith('- ') or l.startswith('! '):
1291 u = '-' + s
1296 u = '-' + s
1292 elif l.startswith(' '):
1297 elif l.startswith(' '):
1293 u = ' ' + s
1298 u = ' ' + s
1294 else:
1299 else:
1295 raise PatchError(_("bad hunk #%d old text line %d") %
1300 raise PatchError(_("bad hunk #%d old text line %d") %
1296 (self.number, x))
1301 (self.number, x))
1297 self.a.append(u)
1302 self.a.append(u)
1298 self.hunk.append(u)
1303 self.hunk.append(u)
1299
1304
1300 l = lr.readline()
1305 l = lr.readline()
1301 if l.startswith('\ '):
1306 if l.startswith('\ '):
1302 s = self.a[-1][:-1]
1307 s = self.a[-1][:-1]
1303 self.a[-1] = s
1308 self.a[-1] = s
1304 self.hunk[-1] = s
1309 self.hunk[-1] = s
1305 l = lr.readline()
1310 l = lr.readline()
1306 m = contextdesc.match(l)
1311 m = contextdesc.match(l)
1307 if not m:
1312 if not m:
1308 raise PatchError(_("bad hunk #%d") % self.number)
1313 raise PatchError(_("bad hunk #%d") % self.number)
1309 self.startb, bend = m.groups()
1314 self.startb, bend = m.groups()
1310 self.startb = int(self.startb)
1315 self.startb = int(self.startb)
1311 if bend is None:
1316 if bend is None:
1312 bend = self.startb
1317 bend = self.startb
1313 self.lenb = int(bend) - self.startb
1318 self.lenb = int(bend) - self.startb
1314 if self.startb:
1319 if self.startb:
1315 self.lenb += 1
1320 self.lenb += 1
1316 hunki = 1
1321 hunki = 1
1317 for x in xrange(self.lenb):
1322 for x in xrange(self.lenb):
1318 l = lr.readline()
1323 l = lr.readline()
1319 if l.startswith('\ '):
1324 if l.startswith('\ '):
1320 # XXX: the only way to hit this is with an invalid line range.
1325 # XXX: the only way to hit this is with an invalid line range.
1321 # The no-eol marker is not counted in the line range, but I
1326 # The no-eol marker is not counted in the line range, but I
1322 # guess there are diff(1) out there which behave differently.
1327 # guess there are diff(1) out there which behave differently.
1323 s = self.b[-1][:-1]
1328 s = self.b[-1][:-1]
1324 self.b[-1] = s
1329 self.b[-1] = s
1325 self.hunk[hunki - 1] = s
1330 self.hunk[hunki - 1] = s
1326 continue
1331 continue
1327 if not l:
1332 if not l:
1328 # line deletions, new block is empty and we hit EOF
1333 # line deletions, new block is empty and we hit EOF
1329 lr.push(l)
1334 lr.push(l)
1330 break
1335 break
1331 s = l[2:]
1336 s = l[2:]
1332 if l.startswith('+ ') or l.startswith('! '):
1337 if l.startswith('+ ') or l.startswith('! '):
1333 u = '+' + s
1338 u = '+' + s
1334 elif l.startswith(' '):
1339 elif l.startswith(' '):
1335 u = ' ' + s
1340 u = ' ' + s
1336 elif len(self.b) == 0:
1341 elif len(self.b) == 0:
1337 # line deletions, new block is empty
1342 # line deletions, new block is empty
1338 lr.push(l)
1343 lr.push(l)
1339 break
1344 break
1340 else:
1345 else:
1341 raise PatchError(_("bad hunk #%d old text line %d") %
1346 raise PatchError(_("bad hunk #%d old text line %d") %
1342 (self.number, x))
1347 (self.number, x))
1343 self.b.append(s)
1348 self.b.append(s)
1344 while True:
1349 while True:
1345 if hunki >= len(self.hunk):
1350 if hunki >= len(self.hunk):
1346 h = ""
1351 h = ""
1347 else:
1352 else:
1348 h = self.hunk[hunki]
1353 h = self.hunk[hunki]
1349 hunki += 1
1354 hunki += 1
1350 if h == u:
1355 if h == u:
1351 break
1356 break
1352 elif h.startswith('-'):
1357 elif h.startswith('-'):
1353 continue
1358 continue
1354 else:
1359 else:
1355 self.hunk.insert(hunki - 1, u)
1360 self.hunk.insert(hunki - 1, u)
1356 break
1361 break
1357
1362
1358 if not self.a:
1363 if not self.a:
1359 # this happens when lines were only added to the hunk
1364 # this happens when lines were only added to the hunk
1360 for x in self.hunk:
1365 for x in self.hunk:
1361 if x.startswith('-') or x.startswith(' '):
1366 if x.startswith('-') or x.startswith(' '):
1362 self.a.append(x)
1367 self.a.append(x)
1363 if not self.b:
1368 if not self.b:
1364 # this happens when lines were only deleted from the hunk
1369 # this happens when lines were only deleted from the hunk
1365 for x in self.hunk:
1370 for x in self.hunk:
1366 if x.startswith('+') or x.startswith(' '):
1371 if x.startswith('+') or x.startswith(' '):
1367 self.b.append(x[1:])
1372 self.b.append(x[1:])
1368 # @@ -start,len +start,len @@
1373 # @@ -start,len +start,len @@
1369 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
1374 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
1370 self.startb, self.lenb)
1375 self.startb, self.lenb)
1371 self.hunk[0] = self.desc
1376 self.hunk[0] = self.desc
1372 self._fixnewline(lr)
1377 self._fixnewline(lr)
1373
1378
1374 def _fixnewline(self, lr):
1379 def _fixnewline(self, lr):
1375 l = lr.readline()
1380 l = lr.readline()
1376 if l.startswith('\ '):
1381 if l.startswith('\ '):
1377 diffhelpers.fixnewline(self.hunk, self.a, self.b)
1382 diffhelpers.fixnewline(self.hunk, self.a, self.b)
1378 else:
1383 else:
1379 lr.push(l)
1384 lr.push(l)
1380
1385
1381 def complete(self):
1386 def complete(self):
1382 return len(self.a) == self.lena and len(self.b) == self.lenb
1387 return len(self.a) == self.lena and len(self.b) == self.lenb
1383
1388
1384 def _fuzzit(self, old, new, fuzz, toponly):
1389 def _fuzzit(self, old, new, fuzz, toponly):
1385 # this removes context lines from the top and bottom of list 'l'. It
1390 # this removes context lines from the top and bottom of list 'l'. It
1386 # checks the hunk to make sure only context lines are removed, and then
1391 # checks the hunk to make sure only context lines are removed, and then
1387 # returns a new shortened list of lines.
1392 # returns a new shortened list of lines.
1388 fuzz = min(fuzz, len(old))
1393 fuzz = min(fuzz, len(old))
1389 if fuzz:
1394 if fuzz:
1390 top = 0
1395 top = 0
1391 bot = 0
1396 bot = 0
1392 hlen = len(self.hunk)
1397 hlen = len(self.hunk)
1393 for x in xrange(hlen - 1):
1398 for x in xrange(hlen - 1):
1394 # the hunk starts with the @@ line, so use x+1
1399 # the hunk starts with the @@ line, so use x+1
1395 if self.hunk[x + 1].startswith(' '):
1400 if self.hunk[x + 1].startswith(' '):
1396 top += 1
1401 top += 1
1397 else:
1402 else:
1398 break
1403 break
1399 if not toponly:
1404 if not toponly:
1400 for x in xrange(hlen - 1):
1405 for x in xrange(hlen - 1):
1401 if self.hunk[hlen - bot - 1].startswith(' '):
1406 if self.hunk[hlen - bot - 1].startswith(' '):
1402 bot += 1
1407 bot += 1
1403 else:
1408 else:
1404 break
1409 break
1405
1410
1406 bot = min(fuzz, bot)
1411 bot = min(fuzz, bot)
1407 top = min(fuzz, top)
1412 top = min(fuzz, top)
1408 return old[top:len(old) - bot], new[top:len(new) - bot], top
1413 return old[top:len(old) - bot], new[top:len(new) - bot], top
1409 return old, new, 0
1414 return old, new, 0
1410
1415
1411 def fuzzit(self, fuzz, toponly):
1416 def fuzzit(self, fuzz, toponly):
1412 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1417 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1413 oldstart = self.starta + top
1418 oldstart = self.starta + top
1414 newstart = self.startb + top
1419 newstart = self.startb + top
1415 # zero length hunk ranges already have their start decremented
1420 # zero length hunk ranges already have their start decremented
1416 if self.lena and oldstart > 0:
1421 if self.lena and oldstart > 0:
1417 oldstart -= 1
1422 oldstart -= 1
1418 if self.lenb and newstart > 0:
1423 if self.lenb and newstart > 0:
1419 newstart -= 1
1424 newstart -= 1
1420 return old, oldstart, new, newstart
1425 return old, oldstart, new, newstart
1421
1426
1422 class binhunk(object):
1427 class binhunk(object):
1423 'A binary patch file.'
1428 'A binary patch file.'
1424 def __init__(self, lr, fname):
1429 def __init__(self, lr, fname):
1425 self.text = None
1430 self.text = None
1426 self.delta = False
1431 self.delta = False
1427 self.hunk = ['GIT binary patch\n']
1432 self.hunk = ['GIT binary patch\n']
1428 self._fname = fname
1433 self._fname = fname
1429 self._read(lr)
1434 self._read(lr)
1430
1435
1431 def complete(self):
1436 def complete(self):
1432 return self.text is not None
1437 return self.text is not None
1433
1438
1434 def new(self, lines):
1439 def new(self, lines):
1435 if self.delta:
1440 if self.delta:
1436 return [applybindelta(self.text, ''.join(lines))]
1441 return [applybindelta(self.text, ''.join(lines))]
1437 return [self.text]
1442 return [self.text]
1438
1443
1439 def _read(self, lr):
1444 def _read(self, lr):
1440 def getline(lr, hunk):
1445 def getline(lr, hunk):
1441 l = lr.readline()
1446 l = lr.readline()
1442 hunk.append(l)
1447 hunk.append(l)
1443 return l.rstrip('\r\n')
1448 return l.rstrip('\r\n')
1444
1449
1445 size = 0
1450 size = 0
1446 while True:
1451 while True:
1447 line = getline(lr, self.hunk)
1452 line = getline(lr, self.hunk)
1448 if not line:
1453 if not line:
1449 raise PatchError(_('could not extract "%s" binary data')
1454 raise PatchError(_('could not extract "%s" binary data')
1450 % self._fname)
1455 % self._fname)
1451 if line.startswith('literal '):
1456 if line.startswith('literal '):
1452 size = int(line[8:].rstrip())
1457 size = int(line[8:].rstrip())
1453 break
1458 break
1454 if line.startswith('delta '):
1459 if line.startswith('delta '):
1455 size = int(line[6:].rstrip())
1460 size = int(line[6:].rstrip())
1456 self.delta = True
1461 self.delta = True
1457 break
1462 break
1458 dec = []
1463 dec = []
1459 line = getline(lr, self.hunk)
1464 line = getline(lr, self.hunk)
1460 while len(line) > 1:
1465 while len(line) > 1:
1461 l = line[0:1]
1466 l = line[0:1]
1462 if l <= 'Z' and l >= 'A':
1467 if l <= 'Z' and l >= 'A':
1463 l = ord(l) - ord('A') + 1
1468 l = ord(l) - ord('A') + 1
1464 else:
1469 else:
1465 l = ord(l) - ord('a') + 27
1470 l = ord(l) - ord('a') + 27
1466 try:
1471 try:
1467 dec.append(util.b85decode(line[1:])[:l])
1472 dec.append(util.b85decode(line[1:])[:l])
1468 except ValueError as e:
1473 except ValueError as e:
1469 raise PatchError(_('could not decode "%s" binary patch: %s')
1474 raise PatchError(_('could not decode "%s" binary patch: %s')
1470 % (self._fname, stringutil.forcebytestr(e)))
1475 % (self._fname, stringutil.forcebytestr(e)))
1471 line = getline(lr, self.hunk)
1476 line = getline(lr, self.hunk)
1472 text = zlib.decompress(''.join(dec))
1477 text = zlib.decompress(''.join(dec))
1473 if len(text) != size:
1478 if len(text) != size:
1474 raise PatchError(_('"%s" length is %d bytes, should be %d')
1479 raise PatchError(_('"%s" length is %d bytes, should be %d')
1475 % (self._fname, len(text), size))
1480 % (self._fname, len(text), size))
1476 self.text = text
1481 self.text = text
1477
1482
1478 def parsefilename(str):
1483 def parsefilename(str):
1479 # --- filename \t|space stuff
1484 # --- filename \t|space stuff
1480 s = str[4:].rstrip('\r\n')
1485 s = str[4:].rstrip('\r\n')
1481 i = s.find('\t')
1486 i = s.find('\t')
1482 if i < 0:
1487 if i < 0:
1483 i = s.find(' ')
1488 i = s.find(' ')
1484 if i < 0:
1489 if i < 0:
1485 return s
1490 return s
1486 return s[:i]
1491 return s[:i]
1487
1492
1488 def reversehunks(hunks):
1493 def reversehunks(hunks):
1489 '''reverse the signs in the hunks given as argument
1494 '''reverse the signs in the hunks given as argument
1490
1495
1491 This function operates on hunks coming out of patch.filterpatch, that is
1496 This function operates on hunks coming out of patch.filterpatch, that is
1492 a list of the form: [header1, hunk1, hunk2, header2...]. Example usage:
1497 a list of the form: [header1, hunk1, hunk2, header2...]. Example usage:
1493
1498
1494 >>> rawpatch = b"""diff --git a/folder1/g b/folder1/g
1499 >>> rawpatch = b"""diff --git a/folder1/g b/folder1/g
1495 ... --- a/folder1/g
1500 ... --- a/folder1/g
1496 ... +++ b/folder1/g
1501 ... +++ b/folder1/g
1497 ... @@ -1,7 +1,7 @@
1502 ... @@ -1,7 +1,7 @@
1498 ... +firstline
1503 ... +firstline
1499 ... c
1504 ... c
1500 ... 1
1505 ... 1
1501 ... 2
1506 ... 2
1502 ... + 3
1507 ... + 3
1503 ... -4
1508 ... -4
1504 ... 5
1509 ... 5
1505 ... d
1510 ... d
1506 ... +lastline"""
1511 ... +lastline"""
1507 >>> hunks = parsepatch([rawpatch])
1512 >>> hunks = parsepatch([rawpatch])
1508 >>> hunkscomingfromfilterpatch = []
1513 >>> hunkscomingfromfilterpatch = []
1509 >>> for h in hunks:
1514 >>> for h in hunks:
1510 ... hunkscomingfromfilterpatch.append(h)
1515 ... hunkscomingfromfilterpatch.append(h)
1511 ... hunkscomingfromfilterpatch.extend(h.hunks)
1516 ... hunkscomingfromfilterpatch.extend(h.hunks)
1512
1517
1513 >>> reversedhunks = reversehunks(hunkscomingfromfilterpatch)
1518 >>> reversedhunks = reversehunks(hunkscomingfromfilterpatch)
1514 >>> from . import util
1519 >>> from . import util
1515 >>> fp = util.stringio()
1520 >>> fp = util.stringio()
1516 >>> for c in reversedhunks:
1521 >>> for c in reversedhunks:
1517 ... c.write(fp)
1522 ... c.write(fp)
1518 >>> fp.seek(0) or None
1523 >>> fp.seek(0) or None
1519 >>> reversedpatch = fp.read()
1524 >>> reversedpatch = fp.read()
1520 >>> print(pycompat.sysstr(reversedpatch))
1525 >>> print(pycompat.sysstr(reversedpatch))
1521 diff --git a/folder1/g b/folder1/g
1526 diff --git a/folder1/g b/folder1/g
1522 --- a/folder1/g
1527 --- a/folder1/g
1523 +++ b/folder1/g
1528 +++ b/folder1/g
1524 @@ -1,4 +1,3 @@
1529 @@ -1,4 +1,3 @@
1525 -firstline
1530 -firstline
1526 c
1531 c
1527 1
1532 1
1528 2
1533 2
1529 @@ -2,6 +1,6 @@
1534 @@ -2,6 +1,6 @@
1530 c
1535 c
1531 1
1536 1
1532 2
1537 2
1533 - 3
1538 - 3
1534 +4
1539 +4
1535 5
1540 5
1536 d
1541 d
1537 @@ -6,3 +5,2 @@
1542 @@ -6,3 +5,2 @@
1538 5
1543 5
1539 d
1544 d
1540 -lastline
1545 -lastline
1541
1546
1542 '''
1547 '''
1543
1548
1544 newhunks = []
1549 newhunks = []
1545 for c in hunks:
1550 for c in hunks:
1546 if util.safehasattr(c, 'reversehunk'):
1551 if util.safehasattr(c, 'reversehunk'):
1547 c = c.reversehunk()
1552 c = c.reversehunk()
1548 newhunks.append(c)
1553 newhunks.append(c)
1549 return newhunks
1554 return newhunks
1550
1555
1551 def parsepatch(originalchunks, maxcontext=None):
1556 def parsepatch(originalchunks, maxcontext=None):
1552 """patch -> [] of headers -> [] of hunks
1557 """patch -> [] of headers -> [] of hunks
1553
1558
1554 If maxcontext is not None, trim context lines if necessary.
1559 If maxcontext is not None, trim context lines if necessary.
1555
1560
1556 >>> rawpatch = b'''diff --git a/folder1/g b/folder1/g
1561 >>> rawpatch = b'''diff --git a/folder1/g b/folder1/g
1557 ... --- a/folder1/g
1562 ... --- a/folder1/g
1558 ... +++ b/folder1/g
1563 ... +++ b/folder1/g
1559 ... @@ -1,8 +1,10 @@
1564 ... @@ -1,8 +1,10 @@
1560 ... 1
1565 ... 1
1561 ... 2
1566 ... 2
1562 ... -3
1567 ... -3
1563 ... 4
1568 ... 4
1564 ... 5
1569 ... 5
1565 ... 6
1570 ... 6
1566 ... +6.1
1571 ... +6.1
1567 ... +6.2
1572 ... +6.2
1568 ... 7
1573 ... 7
1569 ... 8
1574 ... 8
1570 ... +9'''
1575 ... +9'''
1571 >>> out = util.stringio()
1576 >>> out = util.stringio()
1572 >>> headers = parsepatch([rawpatch], maxcontext=1)
1577 >>> headers = parsepatch([rawpatch], maxcontext=1)
1573 >>> for header in headers:
1578 >>> for header in headers:
1574 ... header.write(out)
1579 ... header.write(out)
1575 ... for hunk in header.hunks:
1580 ... for hunk in header.hunks:
1576 ... hunk.write(out)
1581 ... hunk.write(out)
1577 >>> print(pycompat.sysstr(out.getvalue()))
1582 >>> print(pycompat.sysstr(out.getvalue()))
1578 diff --git a/folder1/g b/folder1/g
1583 diff --git a/folder1/g b/folder1/g
1579 --- a/folder1/g
1584 --- a/folder1/g
1580 +++ b/folder1/g
1585 +++ b/folder1/g
1581 @@ -2,3 +2,2 @@
1586 @@ -2,3 +2,2 @@
1582 2
1587 2
1583 -3
1588 -3
1584 4
1589 4
1585 @@ -6,2 +5,4 @@
1590 @@ -6,2 +5,4 @@
1586 6
1591 6
1587 +6.1
1592 +6.1
1588 +6.2
1593 +6.2
1589 7
1594 7
1590 @@ -8,1 +9,2 @@
1595 @@ -8,1 +9,2 @@
1591 8
1596 8
1592 +9
1597 +9
1593 """
1598 """
1594 class parser(object):
1599 class parser(object):
1595 """patch parsing state machine"""
1600 """patch parsing state machine"""
1596 def __init__(self):
1601 def __init__(self):
1597 self.fromline = 0
1602 self.fromline = 0
1598 self.toline = 0
1603 self.toline = 0
1599 self.proc = ''
1604 self.proc = ''
1600 self.header = None
1605 self.header = None
1601 self.context = []
1606 self.context = []
1602 self.before = []
1607 self.before = []
1603 self.hunk = []
1608 self.hunk = []
1604 self.headers = []
1609 self.headers = []
1605
1610
1606 def addrange(self, limits):
1611 def addrange(self, limits):
1607 fromstart, fromend, tostart, toend, proc = limits
1612 fromstart, fromend, tostart, toend, proc = limits
1608 self.fromline = int(fromstart)
1613 self.fromline = int(fromstart)
1609 self.toline = int(tostart)
1614 self.toline = int(tostart)
1610 self.proc = proc
1615 self.proc = proc
1611
1616
1612 def addcontext(self, context):
1617 def addcontext(self, context):
1613 if self.hunk:
1618 if self.hunk:
1614 h = recordhunk(self.header, self.fromline, self.toline,
1619 h = recordhunk(self.header, self.fromline, self.toline,
1615 self.proc, self.before, self.hunk, context, maxcontext)
1620 self.proc, self.before, self.hunk, context, maxcontext)
1616 self.header.hunks.append(h)
1621 self.header.hunks.append(h)
1617 self.fromline += len(self.before) + h.removed
1622 self.fromline += len(self.before) + h.removed
1618 self.toline += len(self.before) + h.added
1623 self.toline += len(self.before) + h.added
1619 self.before = []
1624 self.before = []
1620 self.hunk = []
1625 self.hunk = []
1621 self.context = context
1626 self.context = context
1622
1627
1623 def addhunk(self, hunk):
1628 def addhunk(self, hunk):
1624 if self.context:
1629 if self.context:
1625 self.before = self.context
1630 self.before = self.context
1626 self.context = []
1631 self.context = []
1627 self.hunk = hunk
1632 self.hunk = hunk
1628
1633
1629 def newfile(self, hdr):
1634 def newfile(self, hdr):
1630 self.addcontext([])
1635 self.addcontext([])
1631 h = header(hdr)
1636 h = header(hdr)
1632 self.headers.append(h)
1637 self.headers.append(h)
1633 self.header = h
1638 self.header = h
1634
1639
1635 def addother(self, line):
1640 def addother(self, line):
1636 pass # 'other' lines are ignored
1641 pass # 'other' lines are ignored
1637
1642
1638 def finished(self):
1643 def finished(self):
1639 self.addcontext([])
1644 self.addcontext([])
1640 return self.headers
1645 return self.headers
1641
1646
1642 transitions = {
1647 transitions = {
1643 'file': {'context': addcontext,
1648 'file': {'context': addcontext,
1644 'file': newfile,
1649 'file': newfile,
1645 'hunk': addhunk,
1650 'hunk': addhunk,
1646 'range': addrange},
1651 'range': addrange},
1647 'context': {'file': newfile,
1652 'context': {'file': newfile,
1648 'hunk': addhunk,
1653 'hunk': addhunk,
1649 'range': addrange,
1654 'range': addrange,
1650 'other': addother},
1655 'other': addother},
1651 'hunk': {'context': addcontext,
1656 'hunk': {'context': addcontext,
1652 'file': newfile,
1657 'file': newfile,
1653 'range': addrange},
1658 'range': addrange},
1654 'range': {'context': addcontext,
1659 'range': {'context': addcontext,
1655 'hunk': addhunk},
1660 'hunk': addhunk},
1656 'other': {'other': addother},
1661 'other': {'other': addother},
1657 }
1662 }
1658
1663
1659 p = parser()
1664 p = parser()
1660 fp = stringio()
1665 fp = stringio()
1661 fp.write(''.join(originalchunks))
1666 fp.write(''.join(originalchunks))
1662 fp.seek(0)
1667 fp.seek(0)
1663
1668
1664 state = 'context'
1669 state = 'context'
1665 for newstate, data in scanpatch(fp):
1670 for newstate, data in scanpatch(fp):
1666 try:
1671 try:
1667 p.transitions[state][newstate](p, data)
1672 p.transitions[state][newstate](p, data)
1668 except KeyError:
1673 except KeyError:
1669 raise PatchError('unhandled transition: %s -> %s' %
1674 raise PatchError('unhandled transition: %s -> %s' %
1670 (state, newstate))
1675 (state, newstate))
1671 state = newstate
1676 state = newstate
1672 del fp
1677 del fp
1673 return p.finished()
1678 return p.finished()
1674
1679
1675 def pathtransform(path, strip, prefix):
1680 def pathtransform(path, strip, prefix):
1676 '''turn a path from a patch into a path suitable for the repository
1681 '''turn a path from a patch into a path suitable for the repository
1677
1682
1678 prefix, if not empty, is expected to be normalized with a / at the end.
1683 prefix, if not empty, is expected to be normalized with a / at the end.
1679
1684
1680 Returns (stripped components, path in repository).
1685 Returns (stripped components, path in repository).
1681
1686
1682 >>> pathtransform(b'a/b/c', 0, b'')
1687 >>> pathtransform(b'a/b/c', 0, b'')
1683 ('', 'a/b/c')
1688 ('', 'a/b/c')
1684 >>> pathtransform(b' a/b/c ', 0, b'')
1689 >>> pathtransform(b' a/b/c ', 0, b'')
1685 ('', ' a/b/c')
1690 ('', ' a/b/c')
1686 >>> pathtransform(b' a/b/c ', 2, b'')
1691 >>> pathtransform(b' a/b/c ', 2, b'')
1687 ('a/b/', 'c')
1692 ('a/b/', 'c')
1688 >>> pathtransform(b'a/b/c', 0, b'd/e/')
1693 >>> pathtransform(b'a/b/c', 0, b'd/e/')
1689 ('', 'd/e/a/b/c')
1694 ('', 'd/e/a/b/c')
1690 >>> pathtransform(b' a//b/c ', 2, b'd/e/')
1695 >>> pathtransform(b' a//b/c ', 2, b'd/e/')
1691 ('a//b/', 'd/e/c')
1696 ('a//b/', 'd/e/c')
1692 >>> pathtransform(b'a/b/c', 3, b'')
1697 >>> pathtransform(b'a/b/c', 3, b'')
1693 Traceback (most recent call last):
1698 Traceback (most recent call last):
1694 PatchError: unable to strip away 1 of 3 dirs from a/b/c
1699 PatchError: unable to strip away 1 of 3 dirs from a/b/c
1695 '''
1700 '''
1696 pathlen = len(path)
1701 pathlen = len(path)
1697 i = 0
1702 i = 0
1698 if strip == 0:
1703 if strip == 0:
1699 return '', prefix + path.rstrip()
1704 return '', prefix + path.rstrip()
1700 count = strip
1705 count = strip
1701 while count > 0:
1706 while count > 0:
1702 i = path.find('/', i)
1707 i = path.find('/', i)
1703 if i == -1:
1708 if i == -1:
1704 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1709 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1705 (count, strip, path))
1710 (count, strip, path))
1706 i += 1
1711 i += 1
1707 # consume '//' in the path
1712 # consume '//' in the path
1708 while i < pathlen - 1 and path[i:i + 1] == '/':
1713 while i < pathlen - 1 and path[i:i + 1] == '/':
1709 i += 1
1714 i += 1
1710 count -= 1
1715 count -= 1
1711 return path[:i].lstrip(), prefix + path[i:].rstrip()
1716 return path[:i].lstrip(), prefix + path[i:].rstrip()
1712
1717
1713 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip, prefix):
1718 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip, prefix):
1714 nulla = afile_orig == "/dev/null"
1719 nulla = afile_orig == "/dev/null"
1715 nullb = bfile_orig == "/dev/null"
1720 nullb = bfile_orig == "/dev/null"
1716 create = nulla and hunk.starta == 0 and hunk.lena == 0
1721 create = nulla and hunk.starta == 0 and hunk.lena == 0
1717 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1722 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1718 abase, afile = pathtransform(afile_orig, strip, prefix)
1723 abase, afile = pathtransform(afile_orig, strip, prefix)
1719 gooda = not nulla and backend.exists(afile)
1724 gooda = not nulla and backend.exists(afile)
1720 bbase, bfile = pathtransform(bfile_orig, strip, prefix)
1725 bbase, bfile = pathtransform(bfile_orig, strip, prefix)
1721 if afile == bfile:
1726 if afile == bfile:
1722 goodb = gooda
1727 goodb = gooda
1723 else:
1728 else:
1724 goodb = not nullb and backend.exists(bfile)
1729 goodb = not nullb and backend.exists(bfile)
1725 missing = not goodb and not gooda and not create
1730 missing = not goodb and not gooda and not create
1726
1731
1727 # some diff programs apparently produce patches where the afile is
1732 # some diff programs apparently produce patches where the afile is
1728 # not /dev/null, but afile starts with bfile
1733 # not /dev/null, but afile starts with bfile
1729 abasedir = afile[:afile.rfind('/') + 1]
1734 abasedir = afile[:afile.rfind('/') + 1]
1730 bbasedir = bfile[:bfile.rfind('/') + 1]
1735 bbasedir = bfile[:bfile.rfind('/') + 1]
1731 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1736 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1732 and hunk.starta == 0 and hunk.lena == 0):
1737 and hunk.starta == 0 and hunk.lena == 0):
1733 create = True
1738 create = True
1734 missing = False
1739 missing = False
1735
1740
1736 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1741 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1737 # diff is between a file and its backup. In this case, the original
1742 # diff is between a file and its backup. In this case, the original
1738 # file should be patched (see original mpatch code).
1743 # file should be patched (see original mpatch code).
1739 isbackup = (abase == bbase and bfile.startswith(afile))
1744 isbackup = (abase == bbase and bfile.startswith(afile))
1740 fname = None
1745 fname = None
1741 if not missing:
1746 if not missing:
1742 if gooda and goodb:
1747 if gooda and goodb:
1743 if isbackup:
1748 if isbackup:
1744 fname = afile
1749 fname = afile
1745 else:
1750 else:
1746 fname = bfile
1751 fname = bfile
1747 elif gooda:
1752 elif gooda:
1748 fname = afile
1753 fname = afile
1749
1754
1750 if not fname:
1755 if not fname:
1751 if not nullb:
1756 if not nullb:
1752 if isbackup:
1757 if isbackup:
1753 fname = afile
1758 fname = afile
1754 else:
1759 else:
1755 fname = bfile
1760 fname = bfile
1756 elif not nulla:
1761 elif not nulla:
1757 fname = afile
1762 fname = afile
1758 else:
1763 else:
1759 raise PatchError(_("undefined source and destination files"))
1764 raise PatchError(_("undefined source and destination files"))
1760
1765
1761 gp = patchmeta(fname)
1766 gp = patchmeta(fname)
1762 if create:
1767 if create:
1763 gp.op = 'ADD'
1768 gp.op = 'ADD'
1764 elif remove:
1769 elif remove:
1765 gp.op = 'DELETE'
1770 gp.op = 'DELETE'
1766 return gp
1771 return gp
1767
1772
1768 def scanpatch(fp):
1773 def scanpatch(fp):
1769 """like patch.iterhunks, but yield different events
1774 """like patch.iterhunks, but yield different events
1770
1775
1771 - ('file', [header_lines + fromfile + tofile])
1776 - ('file', [header_lines + fromfile + tofile])
1772 - ('context', [context_lines])
1777 - ('context', [context_lines])
1773 - ('hunk', [hunk_lines])
1778 - ('hunk', [hunk_lines])
1774 - ('range', (-start,len, +start,len, proc))
1779 - ('range', (-start,len, +start,len, proc))
1775 """
1780 """
1776 lines_re = re.compile(br'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
1781 lines_re = re.compile(br'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
1777 lr = linereader(fp)
1782 lr = linereader(fp)
1778
1783
1779 def scanwhile(first, p):
1784 def scanwhile(first, p):
1780 """scan lr while predicate holds"""
1785 """scan lr while predicate holds"""
1781 lines = [first]
1786 lines = [first]
1782 for line in iter(lr.readline, ''):
1787 for line in iter(lr.readline, ''):
1783 if p(line):
1788 if p(line):
1784 lines.append(line)
1789 lines.append(line)
1785 else:
1790 else:
1786 lr.push(line)
1791 lr.push(line)
1787 break
1792 break
1788 return lines
1793 return lines
1789
1794
1790 for line in iter(lr.readline, ''):
1795 for line in iter(lr.readline, ''):
1791 if line.startswith('diff --git a/') or line.startswith('diff -r '):
1796 if line.startswith('diff --git a/') or line.startswith('diff -r '):
1792 def notheader(line):
1797 def notheader(line):
1793 s = line.split(None, 1)
1798 s = line.split(None, 1)
1794 return not s or s[0] not in ('---', 'diff')
1799 return not s or s[0] not in ('---', 'diff')
1795 header = scanwhile(line, notheader)
1800 header = scanwhile(line, notheader)
1796 fromfile = lr.readline()
1801 fromfile = lr.readline()
1797 if fromfile.startswith('---'):
1802 if fromfile.startswith('---'):
1798 tofile = lr.readline()
1803 tofile = lr.readline()
1799 header += [fromfile, tofile]
1804 header += [fromfile, tofile]
1800 else:
1805 else:
1801 lr.push(fromfile)
1806 lr.push(fromfile)
1802 yield 'file', header
1807 yield 'file', header
1803 elif line.startswith(' '):
1808 elif line.startswith(' '):
1804 cs = (' ', '\\')
1809 cs = (' ', '\\')
1805 yield 'context', scanwhile(line, lambda l: l.startswith(cs))
1810 yield 'context', scanwhile(line, lambda l: l.startswith(cs))
1806 elif line.startswith(('-', '+')):
1811 elif line.startswith(('-', '+')):
1807 cs = ('-', '+', '\\')
1812 cs = ('-', '+', '\\')
1808 yield 'hunk', scanwhile(line, lambda l: l.startswith(cs))
1813 yield 'hunk', scanwhile(line, lambda l: l.startswith(cs))
1809 else:
1814 else:
1810 m = lines_re.match(line)
1815 m = lines_re.match(line)
1811 if m:
1816 if m:
1812 yield 'range', m.groups()
1817 yield 'range', m.groups()
1813 else:
1818 else:
1814 yield 'other', line
1819 yield 'other', line
1815
1820
1816 def scangitpatch(lr, firstline):
1821 def scangitpatch(lr, firstline):
1817 """
1822 """
1818 Git patches can emit:
1823 Git patches can emit:
1819 - rename a to b
1824 - rename a to b
1820 - change b
1825 - change b
1821 - copy a to c
1826 - copy a to c
1822 - change c
1827 - change c
1823
1828
1824 We cannot apply this sequence as-is, the renamed 'a' could not be
1829 We cannot apply this sequence as-is, the renamed 'a' could not be
1825 found for it would have been renamed already. And we cannot copy
1830 found for it would have been renamed already. And we cannot copy
1826 from 'b' instead because 'b' would have been changed already. So
1831 from 'b' instead because 'b' would have been changed already. So
1827 we scan the git patch for copy and rename commands so we can
1832 we scan the git patch for copy and rename commands so we can
1828 perform the copies ahead of time.
1833 perform the copies ahead of time.
1829 """
1834 """
1830 pos = 0
1835 pos = 0
1831 try:
1836 try:
1832 pos = lr.fp.tell()
1837 pos = lr.fp.tell()
1833 fp = lr.fp
1838 fp = lr.fp
1834 except IOError:
1839 except IOError:
1835 fp = stringio(lr.fp.read())
1840 fp = stringio(lr.fp.read())
1836 gitlr = linereader(fp)
1841 gitlr = linereader(fp)
1837 gitlr.push(firstline)
1842 gitlr.push(firstline)
1838 gitpatches = readgitpatch(gitlr)
1843 gitpatches = readgitpatch(gitlr)
1839 fp.seek(pos)
1844 fp.seek(pos)
1840 return gitpatches
1845 return gitpatches
1841
1846
1842 def iterhunks(fp):
1847 def iterhunks(fp):
1843 """Read a patch and yield the following events:
1848 """Read a patch and yield the following events:
1844 - ("file", afile, bfile, firsthunk): select a new target file.
1849 - ("file", afile, bfile, firsthunk): select a new target file.
1845 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1850 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1846 "file" event.
1851 "file" event.
1847 - ("git", gitchanges): current diff is in git format, gitchanges
1852 - ("git", gitchanges): current diff is in git format, gitchanges
1848 maps filenames to gitpatch records. Unique event.
1853 maps filenames to gitpatch records. Unique event.
1849 """
1854 """
1850 afile = ""
1855 afile = ""
1851 bfile = ""
1856 bfile = ""
1852 state = None
1857 state = None
1853 hunknum = 0
1858 hunknum = 0
1854 emitfile = newfile = False
1859 emitfile = newfile = False
1855 gitpatches = None
1860 gitpatches = None
1856
1861
1857 # our states
1862 # our states
1858 BFILE = 1
1863 BFILE = 1
1859 context = None
1864 context = None
1860 lr = linereader(fp)
1865 lr = linereader(fp)
1861
1866
1862 for x in iter(lr.readline, ''):
1867 for x in iter(lr.readline, ''):
1863 if state == BFILE and (
1868 if state == BFILE and (
1864 (not context and x.startswith('@'))
1869 (not context and x.startswith('@'))
1865 or (context is not False and x.startswith('***************'))
1870 or (context is not False and x.startswith('***************'))
1866 or x.startswith('GIT binary patch')):
1871 or x.startswith('GIT binary patch')):
1867 gp = None
1872 gp = None
1868 if (gitpatches and
1873 if (gitpatches and
1869 gitpatches[-1].ispatching(afile, bfile)):
1874 gitpatches[-1].ispatching(afile, bfile)):
1870 gp = gitpatches.pop()
1875 gp = gitpatches.pop()
1871 if x.startswith('GIT binary patch'):
1876 if x.startswith('GIT binary patch'):
1872 h = binhunk(lr, gp.path)
1877 h = binhunk(lr, gp.path)
1873 else:
1878 else:
1874 if context is None and x.startswith('***************'):
1879 if context is None and x.startswith('***************'):
1875 context = True
1880 context = True
1876 h = hunk(x, hunknum + 1, lr, context)
1881 h = hunk(x, hunknum + 1, lr, context)
1877 hunknum += 1
1882 hunknum += 1
1878 if emitfile:
1883 if emitfile:
1879 emitfile = False
1884 emitfile = False
1880 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1885 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1881 yield 'hunk', h
1886 yield 'hunk', h
1882 elif x.startswith('diff --git a/'):
1887 elif x.startswith('diff --git a/'):
1883 m = gitre.match(x.rstrip(' \r\n'))
1888 m = gitre.match(x.rstrip(' \r\n'))
1884 if not m:
1889 if not m:
1885 continue
1890 continue
1886 if gitpatches is None:
1891 if gitpatches is None:
1887 # scan whole input for git metadata
1892 # scan whole input for git metadata
1888 gitpatches = scangitpatch(lr, x)
1893 gitpatches = scangitpatch(lr, x)
1889 yield 'git', [g.copy() for g in gitpatches
1894 yield 'git', [g.copy() for g in gitpatches
1890 if g.op in ('COPY', 'RENAME')]
1895 if g.op in ('COPY', 'RENAME')]
1891 gitpatches.reverse()
1896 gitpatches.reverse()
1892 afile = 'a/' + m.group(1)
1897 afile = 'a/' + m.group(1)
1893 bfile = 'b/' + m.group(2)
1898 bfile = 'b/' + m.group(2)
1894 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1899 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1895 gp = gitpatches.pop()
1900 gp = gitpatches.pop()
1896 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1901 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1897 if not gitpatches:
1902 if not gitpatches:
1898 raise PatchError(_('failed to synchronize metadata for "%s"')
1903 raise PatchError(_('failed to synchronize metadata for "%s"')
1899 % afile[2:])
1904 % afile[2:])
1900 gp = gitpatches[-1]
1905 gp = gitpatches[-1]
1901 newfile = True
1906 newfile = True
1902 elif x.startswith('---'):
1907 elif x.startswith('---'):
1903 # check for a unified diff
1908 # check for a unified diff
1904 l2 = lr.readline()
1909 l2 = lr.readline()
1905 if not l2.startswith('+++'):
1910 if not l2.startswith('+++'):
1906 lr.push(l2)
1911 lr.push(l2)
1907 continue
1912 continue
1908 newfile = True
1913 newfile = True
1909 context = False
1914 context = False
1910 afile = parsefilename(x)
1915 afile = parsefilename(x)
1911 bfile = parsefilename(l2)
1916 bfile = parsefilename(l2)
1912 elif x.startswith('***'):
1917 elif x.startswith('***'):
1913 # check for a context diff
1918 # check for a context diff
1914 l2 = lr.readline()
1919 l2 = lr.readline()
1915 if not l2.startswith('---'):
1920 if not l2.startswith('---'):
1916 lr.push(l2)
1921 lr.push(l2)
1917 continue
1922 continue
1918 l3 = lr.readline()
1923 l3 = lr.readline()
1919 lr.push(l3)
1924 lr.push(l3)
1920 if not l3.startswith("***************"):
1925 if not l3.startswith("***************"):
1921 lr.push(l2)
1926 lr.push(l2)
1922 continue
1927 continue
1923 newfile = True
1928 newfile = True
1924 context = True
1929 context = True
1925 afile = parsefilename(x)
1930 afile = parsefilename(x)
1926 bfile = parsefilename(l2)
1931 bfile = parsefilename(l2)
1927
1932
1928 if newfile:
1933 if newfile:
1929 newfile = False
1934 newfile = False
1930 emitfile = True
1935 emitfile = True
1931 state = BFILE
1936 state = BFILE
1932 hunknum = 0
1937 hunknum = 0
1933
1938
1934 while gitpatches:
1939 while gitpatches:
1935 gp = gitpatches.pop()
1940 gp = gitpatches.pop()
1936 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1941 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1937
1942
1938 def applybindelta(binchunk, data):
1943 def applybindelta(binchunk, data):
1939 """Apply a binary delta hunk
1944 """Apply a binary delta hunk
1940 The algorithm used is the algorithm from git's patch-delta.c
1945 The algorithm used is the algorithm from git's patch-delta.c
1941 """
1946 """
1942 def deltahead(binchunk):
1947 def deltahead(binchunk):
1943 i = 0
1948 i = 0
1944 for c in binchunk:
1949 for c in binchunk:
1945 i += 1
1950 i += 1
1946 if not (ord(c) & 0x80):
1951 if not (ord(c) & 0x80):
1947 return i
1952 return i
1948 return i
1953 return i
1949 out = ""
1954 out = ""
1950 s = deltahead(binchunk)
1955 s = deltahead(binchunk)
1951 binchunk = binchunk[s:]
1956 binchunk = binchunk[s:]
1952 s = deltahead(binchunk)
1957 s = deltahead(binchunk)
1953 binchunk = binchunk[s:]
1958 binchunk = binchunk[s:]
1954 i = 0
1959 i = 0
1955 while i < len(binchunk):
1960 while i < len(binchunk):
1956 cmd = ord(binchunk[i])
1961 cmd = ord(binchunk[i])
1957 i += 1
1962 i += 1
1958 if (cmd & 0x80):
1963 if (cmd & 0x80):
1959 offset = 0
1964 offset = 0
1960 size = 0
1965 size = 0
1961 if (cmd & 0x01):
1966 if (cmd & 0x01):
1962 offset = ord(binchunk[i])
1967 offset = ord(binchunk[i])
1963 i += 1
1968 i += 1
1964 if (cmd & 0x02):
1969 if (cmd & 0x02):
1965 offset |= ord(binchunk[i]) << 8
1970 offset |= ord(binchunk[i]) << 8
1966 i += 1
1971 i += 1
1967 if (cmd & 0x04):
1972 if (cmd & 0x04):
1968 offset |= ord(binchunk[i]) << 16
1973 offset |= ord(binchunk[i]) << 16
1969 i += 1
1974 i += 1
1970 if (cmd & 0x08):
1975 if (cmd & 0x08):
1971 offset |= ord(binchunk[i]) << 24
1976 offset |= ord(binchunk[i]) << 24
1972 i += 1
1977 i += 1
1973 if (cmd & 0x10):
1978 if (cmd & 0x10):
1974 size = ord(binchunk[i])
1979 size = ord(binchunk[i])
1975 i += 1
1980 i += 1
1976 if (cmd & 0x20):
1981 if (cmd & 0x20):
1977 size |= ord(binchunk[i]) << 8
1982 size |= ord(binchunk[i]) << 8
1978 i += 1
1983 i += 1
1979 if (cmd & 0x40):
1984 if (cmd & 0x40):
1980 size |= ord(binchunk[i]) << 16
1985 size |= ord(binchunk[i]) << 16
1981 i += 1
1986 i += 1
1982 if size == 0:
1987 if size == 0:
1983 size = 0x10000
1988 size = 0x10000
1984 offset_end = offset + size
1989 offset_end = offset + size
1985 out += data[offset:offset_end]
1990 out += data[offset:offset_end]
1986 elif cmd != 0:
1991 elif cmd != 0:
1987 offset_end = i + cmd
1992 offset_end = i + cmd
1988 out += binchunk[i:offset_end]
1993 out += binchunk[i:offset_end]
1989 i += cmd
1994 i += cmd
1990 else:
1995 else:
1991 raise PatchError(_('unexpected delta opcode 0'))
1996 raise PatchError(_('unexpected delta opcode 0'))
1992 return out
1997 return out
1993
1998
1994 def applydiff(ui, fp, backend, store, strip=1, prefix='', eolmode='strict'):
1999 def applydiff(ui, fp, backend, store, strip=1, prefix='', eolmode='strict'):
1995 """Reads a patch from fp and tries to apply it.
2000 """Reads a patch from fp and tries to apply it.
1996
2001
1997 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
2002 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1998 there was any fuzz.
2003 there was any fuzz.
1999
2004
2000 If 'eolmode' is 'strict', the patch content and patched file are
2005 If 'eolmode' is 'strict', the patch content and patched file are
2001 read in binary mode. Otherwise, line endings are ignored when
2006 read in binary mode. Otherwise, line endings are ignored when
2002 patching then normalized according to 'eolmode'.
2007 patching then normalized according to 'eolmode'.
2003 """
2008 """
2004 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
2009 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
2005 prefix=prefix, eolmode=eolmode)
2010 prefix=prefix, eolmode=eolmode)
2006
2011
2007 def _canonprefix(repo, prefix):
2012 def _canonprefix(repo, prefix):
2008 if prefix:
2013 if prefix:
2009 prefix = pathutil.canonpath(repo.root, repo.getcwd(), prefix)
2014 prefix = pathutil.canonpath(repo.root, repo.getcwd(), prefix)
2010 if prefix != '':
2015 if prefix != '':
2011 prefix += '/'
2016 prefix += '/'
2012 return prefix
2017 return prefix
2013
2018
2014 def _applydiff(ui, fp, patcher, backend, store, strip=1, prefix='',
2019 def _applydiff(ui, fp, patcher, backend, store, strip=1, prefix='',
2015 eolmode='strict'):
2020 eolmode='strict'):
2016 prefix = _canonprefix(backend.repo, prefix)
2021 prefix = _canonprefix(backend.repo, prefix)
2017 def pstrip(p):
2022 def pstrip(p):
2018 return pathtransform(p, strip - 1, prefix)[1]
2023 return pathtransform(p, strip - 1, prefix)[1]
2019
2024
2020 rejects = 0
2025 rejects = 0
2021 err = 0
2026 err = 0
2022 current_file = None
2027 current_file = None
2023
2028
2024 for state, values in iterhunks(fp):
2029 for state, values in iterhunks(fp):
2025 if state == 'hunk':
2030 if state == 'hunk':
2026 if not current_file:
2031 if not current_file:
2027 continue
2032 continue
2028 ret = current_file.apply(values)
2033 ret = current_file.apply(values)
2029 if ret > 0:
2034 if ret > 0:
2030 err = 1
2035 err = 1
2031 elif state == 'file':
2036 elif state == 'file':
2032 if current_file:
2037 if current_file:
2033 rejects += current_file.close()
2038 rejects += current_file.close()
2034 current_file = None
2039 current_file = None
2035 afile, bfile, first_hunk, gp = values
2040 afile, bfile, first_hunk, gp = values
2036 if gp:
2041 if gp:
2037 gp.path = pstrip(gp.path)
2042 gp.path = pstrip(gp.path)
2038 if gp.oldpath:
2043 if gp.oldpath:
2039 gp.oldpath = pstrip(gp.oldpath)
2044 gp.oldpath = pstrip(gp.oldpath)
2040 else:
2045 else:
2041 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
2046 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
2042 prefix)
2047 prefix)
2043 if gp.op == 'RENAME':
2048 if gp.op == 'RENAME':
2044 backend.unlink(gp.oldpath)
2049 backend.unlink(gp.oldpath)
2045 if not first_hunk:
2050 if not first_hunk:
2046 if gp.op == 'DELETE':
2051 if gp.op == 'DELETE':
2047 backend.unlink(gp.path)
2052 backend.unlink(gp.path)
2048 continue
2053 continue
2049 data, mode = None, None
2054 data, mode = None, None
2050 if gp.op in ('RENAME', 'COPY'):
2055 if gp.op in ('RENAME', 'COPY'):
2051 data, mode = store.getfile(gp.oldpath)[:2]
2056 data, mode = store.getfile(gp.oldpath)[:2]
2052 if data is None:
2057 if data is None:
2053 # This means that the old path does not exist
2058 # This means that the old path does not exist
2054 raise PatchError(_("source file '%s' does not exist")
2059 raise PatchError(_("source file '%s' does not exist")
2055 % gp.oldpath)
2060 % gp.oldpath)
2056 if gp.mode:
2061 if gp.mode:
2057 mode = gp.mode
2062 mode = gp.mode
2058 if gp.op == 'ADD':
2063 if gp.op == 'ADD':
2059 # Added files without content have no hunk and
2064 # Added files without content have no hunk and
2060 # must be created
2065 # must be created
2061 data = ''
2066 data = ''
2062 if data or mode:
2067 if data or mode:
2063 if (gp.op in ('ADD', 'RENAME', 'COPY')
2068 if (gp.op in ('ADD', 'RENAME', 'COPY')
2064 and backend.exists(gp.path)):
2069 and backend.exists(gp.path)):
2065 raise PatchError(_("cannot create %s: destination "
2070 raise PatchError(_("cannot create %s: destination "
2066 "already exists") % gp.path)
2071 "already exists") % gp.path)
2067 backend.setfile(gp.path, data, mode, gp.oldpath)
2072 backend.setfile(gp.path, data, mode, gp.oldpath)
2068 continue
2073 continue
2069 try:
2074 try:
2070 current_file = patcher(ui, gp, backend, store,
2075 current_file = patcher(ui, gp, backend, store,
2071 eolmode=eolmode)
2076 eolmode=eolmode)
2072 except PatchError as inst:
2077 except PatchError as inst:
2073 ui.warn(str(inst) + '\n')
2078 ui.warn(str(inst) + '\n')
2074 current_file = None
2079 current_file = None
2075 rejects += 1
2080 rejects += 1
2076 continue
2081 continue
2077 elif state == 'git':
2082 elif state == 'git':
2078 for gp in values:
2083 for gp in values:
2079 path = pstrip(gp.oldpath)
2084 path = pstrip(gp.oldpath)
2080 data, mode = backend.getfile(path)
2085 data, mode = backend.getfile(path)
2081 if data is None:
2086 if data is None:
2082 # The error ignored here will trigger a getfile()
2087 # The error ignored here will trigger a getfile()
2083 # error in a place more appropriate for error
2088 # error in a place more appropriate for error
2084 # handling, and will not interrupt the patching
2089 # handling, and will not interrupt the patching
2085 # process.
2090 # process.
2086 pass
2091 pass
2087 else:
2092 else:
2088 store.setfile(path, data, mode)
2093 store.setfile(path, data, mode)
2089 else:
2094 else:
2090 raise error.Abort(_('unsupported parser state: %s') % state)
2095 raise error.Abort(_('unsupported parser state: %s') % state)
2091
2096
2092 if current_file:
2097 if current_file:
2093 rejects += current_file.close()
2098 rejects += current_file.close()
2094
2099
2095 if rejects:
2100 if rejects:
2096 return -1
2101 return -1
2097 return err
2102 return err
2098
2103
2099 def _externalpatch(ui, repo, patcher, patchname, strip, files,
2104 def _externalpatch(ui, repo, patcher, patchname, strip, files,
2100 similarity):
2105 similarity):
2101 """use <patcher> to apply <patchname> to the working directory.
2106 """use <patcher> to apply <patchname> to the working directory.
2102 returns whether patch was applied with fuzz factor."""
2107 returns whether patch was applied with fuzz factor."""
2103
2108
2104 fuzz = False
2109 fuzz = False
2105 args = []
2110 args = []
2106 cwd = repo.root
2111 cwd = repo.root
2107 if cwd:
2112 if cwd:
2108 args.append('-d %s' % procutil.shellquote(cwd))
2113 args.append('-d %s' % procutil.shellquote(cwd))
2109 cmd = ('%s %s -p%d < %s'
2114 cmd = ('%s %s -p%d < %s'
2110 % (patcher, ' '.join(args), strip, procutil.shellquote(patchname)))
2115 % (patcher, ' '.join(args), strip, procutil.shellquote(patchname)))
2111 fp = procutil.popen(cmd, 'rb')
2116 fp = procutil.popen(cmd, 'rb')
2112 try:
2117 try:
2113 for line in util.iterfile(fp):
2118 for line in util.iterfile(fp):
2114 line = line.rstrip()
2119 line = line.rstrip()
2115 ui.note(line + '\n')
2120 ui.note(line + '\n')
2116 if line.startswith('patching file '):
2121 if line.startswith('patching file '):
2117 pf = util.parsepatchoutput(line)
2122 pf = util.parsepatchoutput(line)
2118 printed_file = False
2123 printed_file = False
2119 files.add(pf)
2124 files.add(pf)
2120 elif line.find('with fuzz') >= 0:
2125 elif line.find('with fuzz') >= 0:
2121 fuzz = True
2126 fuzz = True
2122 if not printed_file:
2127 if not printed_file:
2123 ui.warn(pf + '\n')
2128 ui.warn(pf + '\n')
2124 printed_file = True
2129 printed_file = True
2125 ui.warn(line + '\n')
2130 ui.warn(line + '\n')
2126 elif line.find('saving rejects to file') >= 0:
2131 elif line.find('saving rejects to file') >= 0:
2127 ui.warn(line + '\n')
2132 ui.warn(line + '\n')
2128 elif line.find('FAILED') >= 0:
2133 elif line.find('FAILED') >= 0:
2129 if not printed_file:
2134 if not printed_file:
2130 ui.warn(pf + '\n')
2135 ui.warn(pf + '\n')
2131 printed_file = True
2136 printed_file = True
2132 ui.warn(line + '\n')
2137 ui.warn(line + '\n')
2133 finally:
2138 finally:
2134 if files:
2139 if files:
2135 scmutil.marktouched(repo, files, similarity)
2140 scmutil.marktouched(repo, files, similarity)
2136 code = fp.close()
2141 code = fp.close()
2137 if code:
2142 if code:
2138 raise PatchError(_("patch command failed: %s") %
2143 raise PatchError(_("patch command failed: %s") %
2139 procutil.explainexit(code))
2144 procutil.explainexit(code))
2140 return fuzz
2145 return fuzz
2141
2146
2142 def patchbackend(ui, backend, patchobj, strip, prefix, files=None,
2147 def patchbackend(ui, backend, patchobj, strip, prefix, files=None,
2143 eolmode='strict'):
2148 eolmode='strict'):
2144 if files is None:
2149 if files is None:
2145 files = set()
2150 files = set()
2146 if eolmode is None:
2151 if eolmode is None:
2147 eolmode = ui.config('patch', 'eol')
2152 eolmode = ui.config('patch', 'eol')
2148 if eolmode.lower() not in eolmodes:
2153 if eolmode.lower() not in eolmodes:
2149 raise error.Abort(_('unsupported line endings type: %s') % eolmode)
2154 raise error.Abort(_('unsupported line endings type: %s') % eolmode)
2150 eolmode = eolmode.lower()
2155 eolmode = eolmode.lower()
2151
2156
2152 store = filestore()
2157 store = filestore()
2153 try:
2158 try:
2154 fp = open(patchobj, 'rb')
2159 fp = open(patchobj, 'rb')
2155 except TypeError:
2160 except TypeError:
2156 fp = patchobj
2161 fp = patchobj
2157 try:
2162 try:
2158 ret = applydiff(ui, fp, backend, store, strip=strip, prefix=prefix,
2163 ret = applydiff(ui, fp, backend, store, strip=strip, prefix=prefix,
2159 eolmode=eolmode)
2164 eolmode=eolmode)
2160 finally:
2165 finally:
2161 if fp != patchobj:
2166 if fp != patchobj:
2162 fp.close()
2167 fp.close()
2163 files.update(backend.close())
2168 files.update(backend.close())
2164 store.close()
2169 store.close()
2165 if ret < 0:
2170 if ret < 0:
2166 raise PatchError(_('patch failed to apply'))
2171 raise PatchError(_('patch failed to apply'))
2167 return ret > 0
2172 return ret > 0
2168
2173
2169 def internalpatch(ui, repo, patchobj, strip, prefix='', files=None,
2174 def internalpatch(ui, repo, patchobj, strip, prefix='', files=None,
2170 eolmode='strict', similarity=0):
2175 eolmode='strict', similarity=0):
2171 """use builtin patch to apply <patchobj> to the working directory.
2176 """use builtin patch to apply <patchobj> to the working directory.
2172 returns whether patch was applied with fuzz factor."""
2177 returns whether patch was applied with fuzz factor."""
2173 backend = workingbackend(ui, repo, similarity)
2178 backend = workingbackend(ui, repo, similarity)
2174 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
2179 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
2175
2180
2176 def patchrepo(ui, repo, ctx, store, patchobj, strip, prefix, files=None,
2181 def patchrepo(ui, repo, ctx, store, patchobj, strip, prefix, files=None,
2177 eolmode='strict'):
2182 eolmode='strict'):
2178 backend = repobackend(ui, repo, ctx, store)
2183 backend = repobackend(ui, repo, ctx, store)
2179 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
2184 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
2180
2185
2181 def patch(ui, repo, patchname, strip=1, prefix='', files=None, eolmode='strict',
2186 def patch(ui, repo, patchname, strip=1, prefix='', files=None, eolmode='strict',
2182 similarity=0):
2187 similarity=0):
2183 """Apply <patchname> to the working directory.
2188 """Apply <patchname> to the working directory.
2184
2189
2185 'eolmode' specifies how end of lines should be handled. It can be:
2190 'eolmode' specifies how end of lines should be handled. It can be:
2186 - 'strict': inputs are read in binary mode, EOLs are preserved
2191 - 'strict': inputs are read in binary mode, EOLs are preserved
2187 - 'crlf': EOLs are ignored when patching and reset to CRLF
2192 - 'crlf': EOLs are ignored when patching and reset to CRLF
2188 - 'lf': EOLs are ignored when patching and reset to LF
2193 - 'lf': EOLs are ignored when patching and reset to LF
2189 - None: get it from user settings, default to 'strict'
2194 - None: get it from user settings, default to 'strict'
2190 'eolmode' is ignored when using an external patcher program.
2195 'eolmode' is ignored when using an external patcher program.
2191
2196
2192 Returns whether patch was applied with fuzz factor.
2197 Returns whether patch was applied with fuzz factor.
2193 """
2198 """
2194 patcher = ui.config('ui', 'patch')
2199 patcher = ui.config('ui', 'patch')
2195 if files is None:
2200 if files is None:
2196 files = set()
2201 files = set()
2197 if patcher:
2202 if patcher:
2198 return _externalpatch(ui, repo, patcher, patchname, strip,
2203 return _externalpatch(ui, repo, patcher, patchname, strip,
2199 files, similarity)
2204 files, similarity)
2200 return internalpatch(ui, repo, patchname, strip, prefix, files, eolmode,
2205 return internalpatch(ui, repo, patchname, strip, prefix, files, eolmode,
2201 similarity)
2206 similarity)
2202
2207
2203 def changedfiles(ui, repo, patchpath, strip=1, prefix=''):
2208 def changedfiles(ui, repo, patchpath, strip=1, prefix=''):
2204 backend = fsbackend(ui, repo.root)
2209 backend = fsbackend(ui, repo.root)
2205 prefix = _canonprefix(repo, prefix)
2210 prefix = _canonprefix(repo, prefix)
2206 with open(patchpath, 'rb') as fp:
2211 with open(patchpath, 'rb') as fp:
2207 changed = set()
2212 changed = set()
2208 for state, values in iterhunks(fp):
2213 for state, values in iterhunks(fp):
2209 if state == 'file':
2214 if state == 'file':
2210 afile, bfile, first_hunk, gp = values
2215 afile, bfile, first_hunk, gp = values
2211 if gp:
2216 if gp:
2212 gp.path = pathtransform(gp.path, strip - 1, prefix)[1]
2217 gp.path = pathtransform(gp.path, strip - 1, prefix)[1]
2213 if gp.oldpath:
2218 if gp.oldpath:
2214 gp.oldpath = pathtransform(gp.oldpath, strip - 1,
2219 gp.oldpath = pathtransform(gp.oldpath, strip - 1,
2215 prefix)[1]
2220 prefix)[1]
2216 else:
2221 else:
2217 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
2222 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
2218 prefix)
2223 prefix)
2219 changed.add(gp.path)
2224 changed.add(gp.path)
2220 if gp.op == 'RENAME':
2225 if gp.op == 'RENAME':
2221 changed.add(gp.oldpath)
2226 changed.add(gp.oldpath)
2222 elif state not in ('hunk', 'git'):
2227 elif state not in ('hunk', 'git'):
2223 raise error.Abort(_('unsupported parser state: %s') % state)
2228 raise error.Abort(_('unsupported parser state: %s') % state)
2224 return changed
2229 return changed
2225
2230
2226 class GitDiffRequired(Exception):
2231 class GitDiffRequired(Exception):
2227 pass
2232 pass
2228
2233
2229 def diffallopts(ui, opts=None, untrusted=False, section='diff'):
2234 def diffallopts(ui, opts=None, untrusted=False, section='diff'):
2230 '''return diffopts with all features supported and parsed'''
2235 '''return diffopts with all features supported and parsed'''
2231 return difffeatureopts(ui, opts=opts, untrusted=untrusted, section=section,
2236 return difffeatureopts(ui, opts=opts, untrusted=untrusted, section=section,
2232 git=True, whitespace=True, formatchanging=True)
2237 git=True, whitespace=True, formatchanging=True)
2233
2238
2234 diffopts = diffallopts
2239 diffopts = diffallopts
2235
2240
2236 def difffeatureopts(ui, opts=None, untrusted=False, section='diff', git=False,
2241 def difffeatureopts(ui, opts=None, untrusted=False, section='diff', git=False,
2237 whitespace=False, formatchanging=False):
2242 whitespace=False, formatchanging=False):
2238 '''return diffopts with only opted-in features parsed
2243 '''return diffopts with only opted-in features parsed
2239
2244
2240 Features:
2245 Features:
2241 - git: git-style diffs
2246 - git: git-style diffs
2242 - whitespace: whitespace options like ignoreblanklines and ignorews
2247 - whitespace: whitespace options like ignoreblanklines and ignorews
2243 - formatchanging: options that will likely break or cause correctness issues
2248 - formatchanging: options that will likely break or cause correctness issues
2244 with most diff parsers
2249 with most diff parsers
2245 '''
2250 '''
2246 def get(key, name=None, getter=ui.configbool, forceplain=None):
2251 def get(key, name=None, getter=ui.configbool, forceplain=None):
2247 if opts:
2252 if opts:
2248 v = opts.get(key)
2253 v = opts.get(key)
2249 # diffopts flags are either None-default (which is passed
2254 # diffopts flags are either None-default (which is passed
2250 # through unchanged, so we can identify unset values), or
2255 # through unchanged, so we can identify unset values), or
2251 # some other falsey default (eg --unified, which defaults
2256 # some other falsey default (eg --unified, which defaults
2252 # to an empty string). We only want to override the config
2257 # to an empty string). We only want to override the config
2253 # entries from hgrc with command line values if they
2258 # entries from hgrc with command line values if they
2254 # appear to have been set, which is any truthy value,
2259 # appear to have been set, which is any truthy value,
2255 # True, or False.
2260 # True, or False.
2256 if v or isinstance(v, bool):
2261 if v or isinstance(v, bool):
2257 return v
2262 return v
2258 if forceplain is not None and ui.plain():
2263 if forceplain is not None and ui.plain():
2259 return forceplain
2264 return forceplain
2260 return getter(section, name or key, untrusted=untrusted)
2265 return getter(section, name or key, untrusted=untrusted)
2261
2266
2262 # core options, expected to be understood by every diff parser
2267 # core options, expected to be understood by every diff parser
2263 buildopts = {
2268 buildopts = {
2264 'nodates': get('nodates'),
2269 'nodates': get('nodates'),
2265 'showfunc': get('show_function', 'showfunc'),
2270 'showfunc': get('show_function', 'showfunc'),
2266 'context': get('unified', getter=ui.config),
2271 'context': get('unified', getter=ui.config),
2267 }
2272 }
2268 buildopts['worddiff'] = ui.configbool('experimental', 'worddiff')
2273 buildopts['worddiff'] = ui.configbool('experimental', 'worddiff')
2269 buildopts['xdiff'] = ui.configbool('experimental', 'xdiff')
2274 buildopts['xdiff'] = ui.configbool('experimental', 'xdiff')
2270
2275
2271 if git:
2276 if git:
2272 buildopts['git'] = get('git')
2277 buildopts['git'] = get('git')
2273
2278
2274 # since this is in the experimental section, we need to call
2279 # since this is in the experimental section, we need to call
2275 # ui.configbool directory
2280 # ui.configbool directory
2276 buildopts['showsimilarity'] = ui.configbool('experimental',
2281 buildopts['showsimilarity'] = ui.configbool('experimental',
2277 'extendedheader.similarity')
2282 'extendedheader.similarity')
2278
2283
2279 # need to inspect the ui object instead of using get() since we want to
2284 # need to inspect the ui object instead of using get() since we want to
2280 # test for an int
2285 # test for an int
2281 hconf = ui.config('experimental', 'extendedheader.index')
2286 hconf = ui.config('experimental', 'extendedheader.index')
2282 if hconf is not None:
2287 if hconf is not None:
2283 hlen = None
2288 hlen = None
2284 try:
2289 try:
2285 # the hash config could be an integer (for length of hash) or a
2290 # the hash config could be an integer (for length of hash) or a
2286 # word (e.g. short, full, none)
2291 # word (e.g. short, full, none)
2287 hlen = int(hconf)
2292 hlen = int(hconf)
2288 if hlen < 0 or hlen > 40:
2293 if hlen < 0 or hlen > 40:
2289 msg = _("invalid length for extendedheader.index: '%d'\n")
2294 msg = _("invalid length for extendedheader.index: '%d'\n")
2290 ui.warn(msg % hlen)
2295 ui.warn(msg % hlen)
2291 except ValueError:
2296 except ValueError:
2292 # default value
2297 # default value
2293 if hconf == 'short' or hconf == '':
2298 if hconf == 'short' or hconf == '':
2294 hlen = 12
2299 hlen = 12
2295 elif hconf == 'full':
2300 elif hconf == 'full':
2296 hlen = 40
2301 hlen = 40
2297 elif hconf != 'none':
2302 elif hconf != 'none':
2298 msg = _("invalid value for extendedheader.index: '%s'\n")
2303 msg = _("invalid value for extendedheader.index: '%s'\n")
2299 ui.warn(msg % hconf)
2304 ui.warn(msg % hconf)
2300 finally:
2305 finally:
2301 buildopts['index'] = hlen
2306 buildopts['index'] = hlen
2302
2307
2303 if whitespace:
2308 if whitespace:
2304 buildopts['ignorews'] = get('ignore_all_space', 'ignorews')
2309 buildopts['ignorews'] = get('ignore_all_space', 'ignorews')
2305 buildopts['ignorewsamount'] = get('ignore_space_change',
2310 buildopts['ignorewsamount'] = get('ignore_space_change',
2306 'ignorewsamount')
2311 'ignorewsamount')
2307 buildopts['ignoreblanklines'] = get('ignore_blank_lines',
2312 buildopts['ignoreblanklines'] = get('ignore_blank_lines',
2308 'ignoreblanklines')
2313 'ignoreblanklines')
2309 buildopts['ignorewseol'] = get('ignore_space_at_eol', 'ignorewseol')
2314 buildopts['ignorewseol'] = get('ignore_space_at_eol', 'ignorewseol')
2310 if formatchanging:
2315 if formatchanging:
2311 buildopts['text'] = opts and opts.get('text')
2316 buildopts['text'] = opts and opts.get('text')
2312 binary = None if opts is None else opts.get('binary')
2317 binary = None if opts is None else opts.get('binary')
2313 buildopts['nobinary'] = (not binary if binary is not None
2318 buildopts['nobinary'] = (not binary if binary is not None
2314 else get('nobinary', forceplain=False))
2319 else get('nobinary', forceplain=False))
2315 buildopts['noprefix'] = get('noprefix', forceplain=False)
2320 buildopts['noprefix'] = get('noprefix', forceplain=False)
2316
2321
2317 return mdiff.diffopts(**pycompat.strkwargs(buildopts))
2322 return mdiff.diffopts(**pycompat.strkwargs(buildopts))
2318
2323
2319 def diff(repo, node1=None, node2=None, match=None, changes=None,
2324 def diff(repo, node1=None, node2=None, match=None, changes=None,
2320 opts=None, losedatafn=None, prefix='', relroot='', copy=None,
2325 opts=None, losedatafn=None, prefix='', relroot='', copy=None,
2321 hunksfilterfn=None):
2326 hunksfilterfn=None):
2322 '''yields diff of changes to files between two nodes, or node and
2327 '''yields diff of changes to files between two nodes, or node and
2323 working directory.
2328 working directory.
2324
2329
2325 if node1 is None, use first dirstate parent instead.
2330 if node1 is None, use first dirstate parent instead.
2326 if node2 is None, compare node1 with working directory.
2331 if node2 is None, compare node1 with working directory.
2327
2332
2328 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
2333 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
2329 every time some change cannot be represented with the current
2334 every time some change cannot be represented with the current
2330 patch format. Return False to upgrade to git patch format, True to
2335 patch format. Return False to upgrade to git patch format, True to
2331 accept the loss or raise an exception to abort the diff. It is
2336 accept the loss or raise an exception to abort the diff. It is
2332 called with the name of current file being diffed as 'fn'. If set
2337 called with the name of current file being diffed as 'fn'. If set
2333 to None, patches will always be upgraded to git format when
2338 to None, patches will always be upgraded to git format when
2334 necessary.
2339 necessary.
2335
2340
2336 prefix is a filename prefix that is prepended to all filenames on
2341 prefix is a filename prefix that is prepended to all filenames on
2337 display (used for subrepos).
2342 display (used for subrepos).
2338
2343
2339 relroot, if not empty, must be normalized with a trailing /. Any match
2344 relroot, if not empty, must be normalized with a trailing /. Any match
2340 patterns that fall outside it will be ignored.
2345 patterns that fall outside it will be ignored.
2341
2346
2342 copy, if not empty, should contain mappings {dst@y: src@x} of copy
2347 copy, if not empty, should contain mappings {dst@y: src@x} of copy
2343 information.
2348 information.
2344
2349
2345 hunksfilterfn, if not None, should be a function taking a filectx and
2350 hunksfilterfn, if not None, should be a function taking a filectx and
2346 hunks generator that may yield filtered hunks.
2351 hunks generator that may yield filtered hunks.
2347 '''
2352 '''
2348 for fctx1, fctx2, hdr, hunks in diffhunks(
2353 for fctx1, fctx2, hdr, hunks in diffhunks(
2349 repo, node1=node1, node2=node2,
2354 repo, node1=node1, node2=node2,
2350 match=match, changes=changes, opts=opts,
2355 match=match, changes=changes, opts=opts,
2351 losedatafn=losedatafn, prefix=prefix, relroot=relroot, copy=copy,
2356 losedatafn=losedatafn, prefix=prefix, relroot=relroot, copy=copy,
2352 ):
2357 ):
2353 if hunksfilterfn is not None:
2358 if hunksfilterfn is not None:
2354 # If the file has been removed, fctx2 is None; but this should
2359 # If the file has been removed, fctx2 is None; but this should
2355 # not occur here since we catch removed files early in
2360 # not occur here since we catch removed files early in
2356 # logcmdutil.getlinerangerevs() for 'hg log -L'.
2361 # logcmdutil.getlinerangerevs() for 'hg log -L'.
2357 assert fctx2 is not None, \
2362 assert fctx2 is not None, \
2358 'fctx2 unexpectly None in diff hunks filtering'
2363 'fctx2 unexpectly None in diff hunks filtering'
2359 hunks = hunksfilterfn(fctx2, hunks)
2364 hunks = hunksfilterfn(fctx2, hunks)
2360 text = ''.join(sum((list(hlines) for hrange, hlines in hunks), []))
2365 text = ''.join(sum((list(hlines) for hrange, hlines in hunks), []))
2361 if hdr and (text or len(hdr) > 1):
2366 if hdr and (text or len(hdr) > 1):
2362 yield '\n'.join(hdr) + '\n'
2367 yield '\n'.join(hdr) + '\n'
2363 if text:
2368 if text:
2364 yield text
2369 yield text
2365
2370
2366 def diffhunks(repo, node1=None, node2=None, match=None, changes=None,
2371 def diffhunks(repo, node1=None, node2=None, match=None, changes=None,
2367 opts=None, losedatafn=None, prefix='', relroot='', copy=None):
2372 opts=None, losedatafn=None, prefix='', relroot='', copy=None):
2368 """Yield diff of changes to files in the form of (`header`, `hunks`) tuples
2373 """Yield diff of changes to files in the form of (`header`, `hunks`) tuples
2369 where `header` is a list of diff headers and `hunks` is an iterable of
2374 where `header` is a list of diff headers and `hunks` is an iterable of
2370 (`hunkrange`, `hunklines`) tuples.
2375 (`hunkrange`, `hunklines`) tuples.
2371
2376
2372 See diff() for the meaning of parameters.
2377 See diff() for the meaning of parameters.
2373 """
2378 """
2374
2379
2375 if opts is None:
2380 if opts is None:
2376 opts = mdiff.defaultopts
2381 opts = mdiff.defaultopts
2377
2382
2378 if not node1 and not node2:
2383 if not node1 and not node2:
2379 node1 = repo.dirstate.p1()
2384 node1 = repo.dirstate.p1()
2380
2385
2381 def lrugetfilectx():
2386 def lrugetfilectx():
2382 cache = {}
2387 cache = {}
2383 order = collections.deque()
2388 order = collections.deque()
2384 def getfilectx(f, ctx):
2389 def getfilectx(f, ctx):
2385 fctx = ctx.filectx(f, filelog=cache.get(f))
2390 fctx = ctx.filectx(f, filelog=cache.get(f))
2386 if f not in cache:
2391 if f not in cache:
2387 if len(cache) > 20:
2392 if len(cache) > 20:
2388 del cache[order.popleft()]
2393 del cache[order.popleft()]
2389 cache[f] = fctx.filelog()
2394 cache[f] = fctx.filelog()
2390 else:
2395 else:
2391 order.remove(f)
2396 order.remove(f)
2392 order.append(f)
2397 order.append(f)
2393 return fctx
2398 return fctx
2394 return getfilectx
2399 return getfilectx
2395 getfilectx = lrugetfilectx()
2400 getfilectx = lrugetfilectx()
2396
2401
2397 ctx1 = repo[node1]
2402 ctx1 = repo[node1]
2398 ctx2 = repo[node2]
2403 ctx2 = repo[node2]
2399
2404
2400 relfiltered = False
2405 relfiltered = False
2401 if relroot != '' and match.always():
2406 if relroot != '' and match.always():
2402 # as a special case, create a new matcher with just the relroot
2407 # as a special case, create a new matcher with just the relroot
2403 pats = [relroot]
2408 pats = [relroot]
2404 match = scmutil.match(ctx2, pats, default='path')
2409 match = scmutil.match(ctx2, pats, default='path')
2405 relfiltered = True
2410 relfiltered = True
2406
2411
2407 if not changes:
2412 if not changes:
2408 changes = repo.status(ctx1, ctx2, match=match)
2413 changes = repo.status(ctx1, ctx2, match=match)
2409 modified, added, removed = changes[:3]
2414 modified, added, removed = changes[:3]
2410
2415
2411 if not modified and not added and not removed:
2416 if not modified and not added and not removed:
2412 return []
2417 return []
2413
2418
2414 if repo.ui.debugflag:
2419 if repo.ui.debugflag:
2415 hexfunc = hex
2420 hexfunc = hex
2416 else:
2421 else:
2417 hexfunc = short
2422 hexfunc = short
2418 revs = [hexfunc(node) for node in [ctx1.node(), ctx2.node()] if node]
2423 revs = [hexfunc(node) for node in [ctx1.node(), ctx2.node()] if node]
2419
2424
2420 if copy is None:
2425 if copy is None:
2421 copy = {}
2426 copy = {}
2422 if opts.git or opts.upgrade:
2427 if opts.git or opts.upgrade:
2423 copy = copies.pathcopies(ctx1, ctx2, match=match)
2428 copy = copies.pathcopies(ctx1, ctx2, match=match)
2424
2429
2425 if relroot is not None:
2430 if relroot is not None:
2426 if not relfiltered:
2431 if not relfiltered:
2427 # XXX this would ideally be done in the matcher, but that is
2432 # XXX this would ideally be done in the matcher, but that is
2428 # generally meant to 'or' patterns, not 'and' them. In this case we
2433 # generally meant to 'or' patterns, not 'and' them. In this case we
2429 # need to 'and' all the patterns from the matcher with relroot.
2434 # need to 'and' all the patterns from the matcher with relroot.
2430 def filterrel(l):
2435 def filterrel(l):
2431 return [f for f in l if f.startswith(relroot)]
2436 return [f for f in l if f.startswith(relroot)]
2432 modified = filterrel(modified)
2437 modified = filterrel(modified)
2433 added = filterrel(added)
2438 added = filterrel(added)
2434 removed = filterrel(removed)
2439 removed = filterrel(removed)
2435 relfiltered = True
2440 relfiltered = True
2436 # filter out copies where either side isn't inside the relative root
2441 # filter out copies where either side isn't inside the relative root
2437 copy = dict(((dst, src) for (dst, src) in copy.iteritems()
2442 copy = dict(((dst, src) for (dst, src) in copy.iteritems()
2438 if dst.startswith(relroot)
2443 if dst.startswith(relroot)
2439 and src.startswith(relroot)))
2444 and src.startswith(relroot)))
2440
2445
2441 modifiedset = set(modified)
2446 modifiedset = set(modified)
2442 addedset = set(added)
2447 addedset = set(added)
2443 removedset = set(removed)
2448 removedset = set(removed)
2444 for f in modified:
2449 for f in modified:
2445 if f not in ctx1:
2450 if f not in ctx1:
2446 # Fix up added, since merged-in additions appear as
2451 # Fix up added, since merged-in additions appear as
2447 # modifications during merges
2452 # modifications during merges
2448 modifiedset.remove(f)
2453 modifiedset.remove(f)
2449 addedset.add(f)
2454 addedset.add(f)
2450 for f in removed:
2455 for f in removed:
2451 if f not in ctx1:
2456 if f not in ctx1:
2452 # Merged-in additions that are then removed are reported as removed.
2457 # Merged-in additions that are then removed are reported as removed.
2453 # They are not in ctx1, so We don't want to show them in the diff.
2458 # They are not in ctx1, so We don't want to show them in the diff.
2454 removedset.remove(f)
2459 removedset.remove(f)
2455 modified = sorted(modifiedset)
2460 modified = sorted(modifiedset)
2456 added = sorted(addedset)
2461 added = sorted(addedset)
2457 removed = sorted(removedset)
2462 removed = sorted(removedset)
2458 for dst, src in list(copy.items()):
2463 for dst, src in list(copy.items()):
2459 if src not in ctx1:
2464 if src not in ctx1:
2460 # Files merged in during a merge and then copied/renamed are
2465 # Files merged in during a merge and then copied/renamed are
2461 # reported as copies. We want to show them in the diff as additions.
2466 # reported as copies. We want to show them in the diff as additions.
2462 del copy[dst]
2467 del copy[dst]
2463
2468
2464 def difffn(opts, losedata):
2469 def difffn(opts, losedata):
2465 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2470 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2466 copy, getfilectx, opts, losedata, prefix, relroot)
2471 copy, getfilectx, opts, losedata, prefix, relroot)
2467 if opts.upgrade and not opts.git:
2472 if opts.upgrade and not opts.git:
2468 try:
2473 try:
2469 def losedata(fn):
2474 def losedata(fn):
2470 if not losedatafn or not losedatafn(fn=fn):
2475 if not losedatafn or not losedatafn(fn=fn):
2471 raise GitDiffRequired
2476 raise GitDiffRequired
2472 # Buffer the whole output until we are sure it can be generated
2477 # Buffer the whole output until we are sure it can be generated
2473 return list(difffn(opts.copy(git=False), losedata))
2478 return list(difffn(opts.copy(git=False), losedata))
2474 except GitDiffRequired:
2479 except GitDiffRequired:
2475 return difffn(opts.copy(git=True), None)
2480 return difffn(opts.copy(git=True), None)
2476 else:
2481 else:
2477 return difffn(opts, None)
2482 return difffn(opts, None)
2478
2483
2479 def difflabel(func, *args, **kw):
2484 def difflabel(func, *args, **kw):
2480 '''yields 2-tuples of (output, label) based on the output of func()'''
2485 '''yields 2-tuples of (output, label) based on the output of func()'''
2481 inlinecolor = False
2486 inlinecolor = False
2482 if kw.get(r'opts'):
2487 if kw.get(r'opts'):
2483 inlinecolor = kw[r'opts'].worddiff
2488 inlinecolor = kw[r'opts'].worddiff
2484 headprefixes = [('diff', 'diff.diffline'),
2489 headprefixes = [('diff', 'diff.diffline'),
2485 ('copy', 'diff.extended'),
2490 ('copy', 'diff.extended'),
2486 ('rename', 'diff.extended'),
2491 ('rename', 'diff.extended'),
2487 ('old', 'diff.extended'),
2492 ('old', 'diff.extended'),
2488 ('new', 'diff.extended'),
2493 ('new', 'diff.extended'),
2489 ('deleted', 'diff.extended'),
2494 ('deleted', 'diff.extended'),
2490 ('index', 'diff.extended'),
2495 ('index', 'diff.extended'),
2491 ('similarity', 'diff.extended'),
2496 ('similarity', 'diff.extended'),
2492 ('---', 'diff.file_a'),
2497 ('---', 'diff.file_a'),
2493 ('+++', 'diff.file_b')]
2498 ('+++', 'diff.file_b')]
2494 textprefixes = [('@', 'diff.hunk'),
2499 textprefixes = [('@', 'diff.hunk'),
2495 ('-', 'diff.deleted'),
2500 ('-', 'diff.deleted'),
2496 ('+', 'diff.inserted')]
2501 ('+', 'diff.inserted')]
2497 head = False
2502 head = False
2498 for chunk in func(*args, **kw):
2503 for chunk in func(*args, **kw):
2499 lines = chunk.split('\n')
2504 lines = chunk.split('\n')
2500 matches = {}
2505 matches = {}
2501 if inlinecolor:
2506 if inlinecolor:
2502 matches = _findmatches(lines)
2507 matches = _findmatches(lines)
2503 for i, line in enumerate(lines):
2508 for i, line in enumerate(lines):
2504 if i != 0:
2509 if i != 0:
2505 yield ('\n', '')
2510 yield ('\n', '')
2506 if head:
2511 if head:
2507 if line.startswith('@'):
2512 if line.startswith('@'):
2508 head = False
2513 head = False
2509 else:
2514 else:
2510 if line and not line.startswith((' ', '+', '-', '@', '\\')):
2515 if line and not line.startswith((' ', '+', '-', '@', '\\')):
2511 head = True
2516 head = True
2512 stripline = line
2517 stripline = line
2513 diffline = False
2518 diffline = False
2514 if not head and line and line.startswith(('+', '-')):
2519 if not head and line and line.startswith(('+', '-')):
2515 # highlight tabs and trailing whitespace, but only in
2520 # highlight tabs and trailing whitespace, but only in
2516 # changed lines
2521 # changed lines
2517 stripline = line.rstrip()
2522 stripline = line.rstrip()
2518 diffline = True
2523 diffline = True
2519
2524
2520 prefixes = textprefixes
2525 prefixes = textprefixes
2521 if head:
2526 if head:
2522 prefixes = headprefixes
2527 prefixes = headprefixes
2523 for prefix, label in prefixes:
2528 for prefix, label in prefixes:
2524 if stripline.startswith(prefix):
2529 if stripline.startswith(prefix):
2525 if diffline:
2530 if diffline:
2526 if i in matches:
2531 if i in matches:
2527 for t, l in _inlinediff(lines[i].rstrip(),
2532 for t, l in _inlinediff(lines[i].rstrip(),
2528 lines[matches[i]].rstrip(),
2533 lines[matches[i]].rstrip(),
2529 label):
2534 label):
2530 yield (t, l)
2535 yield (t, l)
2531 else:
2536 else:
2532 for token in tabsplitter.findall(stripline):
2537 for token in tabsplitter.findall(stripline):
2533 if token.startswith('\t'):
2538 if token.startswith('\t'):
2534 yield (token, 'diff.tab')
2539 yield (token, 'diff.tab')
2535 else:
2540 else:
2536 yield (token, label)
2541 yield (token, label)
2537 else:
2542 else:
2538 yield (stripline, label)
2543 yield (stripline, label)
2539 break
2544 break
2540 else:
2545 else:
2541 yield (line, '')
2546 yield (line, '')
2542 if line != stripline:
2547 if line != stripline:
2543 yield (line[len(stripline):], 'diff.trailingwhitespace')
2548 yield (line[len(stripline):], 'diff.trailingwhitespace')
2544
2549
2545 def _findmatches(slist):
2550 def _findmatches(slist):
2546 '''Look for insertion matches to deletion and returns a dict of
2551 '''Look for insertion matches to deletion and returns a dict of
2547 correspondences.
2552 correspondences.
2548 '''
2553 '''
2549 lastmatch = 0
2554 lastmatch = 0
2550 matches = {}
2555 matches = {}
2551 for i, line in enumerate(slist):
2556 for i, line in enumerate(slist):
2552 if line == '':
2557 if line == '':
2553 continue
2558 continue
2554 if line.startswith('-'):
2559 if line.startswith('-'):
2555 lastmatch = max(lastmatch, i)
2560 lastmatch = max(lastmatch, i)
2556 newgroup = False
2561 newgroup = False
2557 for j, newline in enumerate(slist[lastmatch + 1:]):
2562 for j, newline in enumerate(slist[lastmatch + 1:]):
2558 if newline == '':
2563 if newline == '':
2559 continue
2564 continue
2560 if newline.startswith('-') and newgroup: # too far, no match
2565 if newline.startswith('-') and newgroup: # too far, no match
2561 break
2566 break
2562 if newline.startswith('+'): # potential match
2567 if newline.startswith('+'): # potential match
2563 newgroup = True
2568 newgroup = True
2564 sim = difflib.SequenceMatcher(None, line, newline).ratio()
2569 sim = difflib.SequenceMatcher(None, line, newline).ratio()
2565 if sim > 0.7:
2570 if sim > 0.7:
2566 lastmatch = lastmatch + 1 + j
2571 lastmatch = lastmatch + 1 + j
2567 matches[i] = lastmatch
2572 matches[i] = lastmatch
2568 matches[lastmatch] = i
2573 matches[lastmatch] = i
2569 break
2574 break
2570 return matches
2575 return matches
2571
2576
2572 def _inlinediff(s1, s2, operation):
2577 def _inlinediff(s1, s2, operation):
2573 '''Perform string diff to highlight specific changes.'''
2578 '''Perform string diff to highlight specific changes.'''
2574 operation_skip = ('+', '?') if operation == 'diff.deleted' else ('-', '?')
2579 operation_skip = ('+', '?') if operation == 'diff.deleted' else ('-', '?')
2575 if operation == 'diff.deleted':
2580 if operation == 'diff.deleted':
2576 s2, s1 = s1, s2
2581 s2, s1 = s1, s2
2577
2582
2578 buff = []
2583 buff = []
2579 # we never want to higlight the leading +-
2584 # we never want to higlight the leading +-
2580 if operation == 'diff.deleted' and s2.startswith('-'):
2585 if operation == 'diff.deleted' and s2.startswith('-'):
2581 label = operation
2586 label = operation
2582 token = '-'
2587 token = '-'
2583 s2 = s2[1:]
2588 s2 = s2[1:]
2584 s1 = s1[1:]
2589 s1 = s1[1:]
2585 elif operation == 'diff.inserted' and s1.startswith('+'):
2590 elif operation == 'diff.inserted' and s1.startswith('+'):
2586 label = operation
2591 label = operation
2587 token = '+'
2592 token = '+'
2588 s2 = s2[1:]
2593 s2 = s2[1:]
2589 s1 = s1[1:]
2594 s1 = s1[1:]
2590 else:
2595 else:
2591 raise error.ProgrammingError("Case not expected, operation = %s" %
2596 raise error.ProgrammingError("Case not expected, operation = %s" %
2592 operation)
2597 operation)
2593
2598
2594 s = difflib.ndiff(_nonwordre.split(s2), _nonwordre.split(s1))
2599 s = difflib.ndiff(_nonwordre.split(s2), _nonwordre.split(s1))
2595 for part in s:
2600 for part in s:
2596 if part.startswith(operation_skip) or len(part) == 2:
2601 if part.startswith(operation_skip) or len(part) == 2:
2597 continue
2602 continue
2598 l = operation + '.highlight'
2603 l = operation + '.highlight'
2599 if part.startswith(' '):
2604 if part.startswith(' '):
2600 l = operation
2605 l = operation
2601 if part[2:] == '\t':
2606 if part[2:] == '\t':
2602 l = 'diff.tab'
2607 l = 'diff.tab'
2603 if l == label: # contiguous token with same label
2608 if l == label: # contiguous token with same label
2604 token += part[2:]
2609 token += part[2:]
2605 continue
2610 continue
2606 else:
2611 else:
2607 buff.append((token, label))
2612 buff.append((token, label))
2608 label = l
2613 label = l
2609 token = part[2:]
2614 token = part[2:]
2610 buff.append((token, label))
2615 buff.append((token, label))
2611
2616
2612 return buff
2617 return buff
2613
2618
2614 def diffui(*args, **kw):
2619 def diffui(*args, **kw):
2615 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
2620 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
2616 return difflabel(diff, *args, **kw)
2621 return difflabel(diff, *args, **kw)
2617
2622
2618 def _filepairs(modified, added, removed, copy, opts):
2623 def _filepairs(modified, added, removed, copy, opts):
2619 '''generates tuples (f1, f2, copyop), where f1 is the name of the file
2624 '''generates tuples (f1, f2, copyop), where f1 is the name of the file
2620 before and f2 is the the name after. For added files, f1 will be None,
2625 before and f2 is the the name after. For added files, f1 will be None,
2621 and for removed files, f2 will be None. copyop may be set to None, 'copy'
2626 and for removed files, f2 will be None. copyop may be set to None, 'copy'
2622 or 'rename' (the latter two only if opts.git is set).'''
2627 or 'rename' (the latter two only if opts.git is set).'''
2623 gone = set()
2628 gone = set()
2624
2629
2625 copyto = dict([(v, k) for k, v in copy.items()])
2630 copyto = dict([(v, k) for k, v in copy.items()])
2626
2631
2627 addedset, removedset = set(added), set(removed)
2632 addedset, removedset = set(added), set(removed)
2628
2633
2629 for f in sorted(modified + added + removed):
2634 for f in sorted(modified + added + removed):
2630 copyop = None
2635 copyop = None
2631 f1, f2 = f, f
2636 f1, f2 = f, f
2632 if f in addedset:
2637 if f in addedset:
2633 f1 = None
2638 f1 = None
2634 if f in copy:
2639 if f in copy:
2635 if opts.git:
2640 if opts.git:
2636 f1 = copy[f]
2641 f1 = copy[f]
2637 if f1 in removedset and f1 not in gone:
2642 if f1 in removedset and f1 not in gone:
2638 copyop = 'rename'
2643 copyop = 'rename'
2639 gone.add(f1)
2644 gone.add(f1)
2640 else:
2645 else:
2641 copyop = 'copy'
2646 copyop = 'copy'
2642 elif f in removedset:
2647 elif f in removedset:
2643 f2 = None
2648 f2 = None
2644 if opts.git:
2649 if opts.git:
2645 # have we already reported a copy above?
2650 # have we already reported a copy above?
2646 if (f in copyto and copyto[f] in addedset
2651 if (f in copyto and copyto[f] in addedset
2647 and copy[copyto[f]] == f):
2652 and copy[copyto[f]] == f):
2648 continue
2653 continue
2649 yield f1, f2, copyop
2654 yield f1, f2, copyop
2650
2655
2651 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2656 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2652 copy, getfilectx, opts, losedatafn, prefix, relroot):
2657 copy, getfilectx, opts, losedatafn, prefix, relroot):
2653 '''given input data, generate a diff and yield it in blocks
2658 '''given input data, generate a diff and yield it in blocks
2654
2659
2655 If generating a diff would lose data like flags or binary data and
2660 If generating a diff would lose data like flags or binary data and
2656 losedatafn is not None, it will be called.
2661 losedatafn is not None, it will be called.
2657
2662
2658 relroot is removed and prefix is added to every path in the diff output.
2663 relroot is removed and prefix is added to every path in the diff output.
2659
2664
2660 If relroot is not empty, this function expects every path in modified,
2665 If relroot is not empty, this function expects every path in modified,
2661 added, removed and copy to start with it.'''
2666 added, removed and copy to start with it.'''
2662
2667
2663 def gitindex(text):
2668 def gitindex(text):
2664 if not text:
2669 if not text:
2665 text = ""
2670 text = ""
2666 l = len(text)
2671 l = len(text)
2667 s = hashlib.sha1('blob %d\0' % l)
2672 s = hashlib.sha1('blob %d\0' % l)
2668 s.update(text)
2673 s.update(text)
2669 return hex(s.digest())
2674 return hex(s.digest())
2670
2675
2671 if opts.noprefix:
2676 if opts.noprefix:
2672 aprefix = bprefix = ''
2677 aprefix = bprefix = ''
2673 else:
2678 else:
2674 aprefix = 'a/'
2679 aprefix = 'a/'
2675 bprefix = 'b/'
2680 bprefix = 'b/'
2676
2681
2677 def diffline(f, revs):
2682 def diffline(f, revs):
2678 revinfo = ' '.join(["-r %s" % rev for rev in revs])
2683 revinfo = ' '.join(["-r %s" % rev for rev in revs])
2679 return 'diff %s %s' % (revinfo, f)
2684 return 'diff %s %s' % (revinfo, f)
2680
2685
2681 def isempty(fctx):
2686 def isempty(fctx):
2682 return fctx is None or fctx.size() == 0
2687 return fctx is None or fctx.size() == 0
2683
2688
2684 date1 = dateutil.datestr(ctx1.date())
2689 date1 = dateutil.datestr(ctx1.date())
2685 date2 = dateutil.datestr(ctx2.date())
2690 date2 = dateutil.datestr(ctx2.date())
2686
2691
2687 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
2692 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
2688
2693
2689 if relroot != '' and (repo.ui.configbool('devel', 'all-warnings')
2694 if relroot != '' and (repo.ui.configbool('devel', 'all-warnings')
2690 or repo.ui.configbool('devel', 'check-relroot')):
2695 or repo.ui.configbool('devel', 'check-relroot')):
2691 for f in modified + added + removed + list(copy) + list(copy.values()):
2696 for f in modified + added + removed + list(copy) + list(copy.values()):
2692 if f is not None and not f.startswith(relroot):
2697 if f is not None and not f.startswith(relroot):
2693 raise AssertionError(
2698 raise AssertionError(
2694 "file %s doesn't start with relroot %s" % (f, relroot))
2699 "file %s doesn't start with relroot %s" % (f, relroot))
2695
2700
2696 for f1, f2, copyop in _filepairs(modified, added, removed, copy, opts):
2701 for f1, f2, copyop in _filepairs(modified, added, removed, copy, opts):
2697 content1 = None
2702 content1 = None
2698 content2 = None
2703 content2 = None
2699 fctx1 = None
2704 fctx1 = None
2700 fctx2 = None
2705 fctx2 = None
2701 flag1 = None
2706 flag1 = None
2702 flag2 = None
2707 flag2 = None
2703 if f1:
2708 if f1:
2704 fctx1 = getfilectx(f1, ctx1)
2709 fctx1 = getfilectx(f1, ctx1)
2705 if opts.git or losedatafn:
2710 if opts.git or losedatafn:
2706 flag1 = ctx1.flags(f1)
2711 flag1 = ctx1.flags(f1)
2707 if f2:
2712 if f2:
2708 fctx2 = getfilectx(f2, ctx2)
2713 fctx2 = getfilectx(f2, ctx2)
2709 if opts.git or losedatafn:
2714 if opts.git or losedatafn:
2710 flag2 = ctx2.flags(f2)
2715 flag2 = ctx2.flags(f2)
2711 # if binary is True, output "summary" or "base85", but not "text diff"
2716 # if binary is True, output "summary" or "base85", but not "text diff"
2712 if opts.text:
2717 if opts.text:
2713 binary = False
2718 binary = False
2714 else:
2719 else:
2715 binary = any(f.isbinary() for f in [fctx1, fctx2] if f is not None)
2720 binary = any(f.isbinary() for f in [fctx1, fctx2] if f is not None)
2716
2721
2717 if losedatafn and not opts.git:
2722 if losedatafn and not opts.git:
2718 if (binary or
2723 if (binary or
2719 # copy/rename
2724 # copy/rename
2720 f2 in copy or
2725 f2 in copy or
2721 # empty file creation
2726 # empty file creation
2722 (not f1 and isempty(fctx2)) or
2727 (not f1 and isempty(fctx2)) or
2723 # empty file deletion
2728 # empty file deletion
2724 (isempty(fctx1) and not f2) or
2729 (isempty(fctx1) and not f2) or
2725 # create with flags
2730 # create with flags
2726 (not f1 and flag2) or
2731 (not f1 and flag2) or
2727 # change flags
2732 # change flags
2728 (f1 and f2 and flag1 != flag2)):
2733 (f1 and f2 and flag1 != flag2)):
2729 losedatafn(f2 or f1)
2734 losedatafn(f2 or f1)
2730
2735
2731 path1 = f1 or f2
2736 path1 = f1 or f2
2732 path2 = f2 or f1
2737 path2 = f2 or f1
2733 path1 = posixpath.join(prefix, path1[len(relroot):])
2738 path1 = posixpath.join(prefix, path1[len(relroot):])
2734 path2 = posixpath.join(prefix, path2[len(relroot):])
2739 path2 = posixpath.join(prefix, path2[len(relroot):])
2735 header = []
2740 header = []
2736 if opts.git:
2741 if opts.git:
2737 header.append('diff --git %s%s %s%s' %
2742 header.append('diff --git %s%s %s%s' %
2738 (aprefix, path1, bprefix, path2))
2743 (aprefix, path1, bprefix, path2))
2739 if not f1: # added
2744 if not f1: # added
2740 header.append('new file mode %s' % gitmode[flag2])
2745 header.append('new file mode %s' % gitmode[flag2])
2741 elif not f2: # removed
2746 elif not f2: # removed
2742 header.append('deleted file mode %s' % gitmode[flag1])
2747 header.append('deleted file mode %s' % gitmode[flag1])
2743 else: # modified/copied/renamed
2748 else: # modified/copied/renamed
2744 mode1, mode2 = gitmode[flag1], gitmode[flag2]
2749 mode1, mode2 = gitmode[flag1], gitmode[flag2]
2745 if mode1 != mode2:
2750 if mode1 != mode2:
2746 header.append('old mode %s' % mode1)
2751 header.append('old mode %s' % mode1)
2747 header.append('new mode %s' % mode2)
2752 header.append('new mode %s' % mode2)
2748 if copyop is not None:
2753 if copyop is not None:
2749 if opts.showsimilarity:
2754 if opts.showsimilarity:
2750 sim = similar.score(ctx1[path1], ctx2[path2]) * 100
2755 sim = similar.score(ctx1[path1], ctx2[path2]) * 100
2751 header.append('similarity index %d%%' % sim)
2756 header.append('similarity index %d%%' % sim)
2752 header.append('%s from %s' % (copyop, path1))
2757 header.append('%s from %s' % (copyop, path1))
2753 header.append('%s to %s' % (copyop, path2))
2758 header.append('%s to %s' % (copyop, path2))
2754 elif revs and not repo.ui.quiet:
2759 elif revs and not repo.ui.quiet:
2755 header.append(diffline(path1, revs))
2760 header.append(diffline(path1, revs))
2756
2761
2757 # fctx.is | diffopts | what to | is fctx.data()
2762 # fctx.is | diffopts | what to | is fctx.data()
2758 # binary() | text nobinary git index | output? | outputted?
2763 # binary() | text nobinary git index | output? | outputted?
2759 # ------------------------------------|----------------------------
2764 # ------------------------------------|----------------------------
2760 # yes | no no no * | summary | no
2765 # yes | no no no * | summary | no
2761 # yes | no no yes * | base85 | yes
2766 # yes | no no yes * | base85 | yes
2762 # yes | no yes no * | summary | no
2767 # yes | no yes no * | summary | no
2763 # yes | no yes yes 0 | summary | no
2768 # yes | no yes yes 0 | summary | no
2764 # yes | no yes yes >0 | summary | semi [1]
2769 # yes | no yes yes >0 | summary | semi [1]
2765 # yes | yes * * * | text diff | yes
2770 # yes | yes * * * | text diff | yes
2766 # no | * * * * | text diff | yes
2771 # no | * * * * | text diff | yes
2767 # [1]: hash(fctx.data()) is outputted. so fctx.data() cannot be faked
2772 # [1]: hash(fctx.data()) is outputted. so fctx.data() cannot be faked
2768 if binary and (not opts.git or (opts.git and opts.nobinary and not
2773 if binary and (not opts.git or (opts.git and opts.nobinary and not
2769 opts.index)):
2774 opts.index)):
2770 # fast path: no binary content will be displayed, content1 and
2775 # fast path: no binary content will be displayed, content1 and
2771 # content2 are only used for equivalent test. cmp() could have a
2776 # content2 are only used for equivalent test. cmp() could have a
2772 # fast path.
2777 # fast path.
2773 if fctx1 is not None:
2778 if fctx1 is not None:
2774 content1 = b'\0'
2779 content1 = b'\0'
2775 if fctx2 is not None:
2780 if fctx2 is not None:
2776 if fctx1 is not None and not fctx1.cmp(fctx2):
2781 if fctx1 is not None and not fctx1.cmp(fctx2):
2777 content2 = b'\0' # not different
2782 content2 = b'\0' # not different
2778 else:
2783 else:
2779 content2 = b'\0\0'
2784 content2 = b'\0\0'
2780 else:
2785 else:
2781 # normal path: load contents
2786 # normal path: load contents
2782 if fctx1 is not None:
2787 if fctx1 is not None:
2783 content1 = fctx1.data()
2788 content1 = fctx1.data()
2784 if fctx2 is not None:
2789 if fctx2 is not None:
2785 content2 = fctx2.data()
2790 content2 = fctx2.data()
2786
2791
2787 if binary and opts.git and not opts.nobinary:
2792 if binary and opts.git and not opts.nobinary:
2788 text = mdiff.b85diff(content1, content2)
2793 text = mdiff.b85diff(content1, content2)
2789 if text:
2794 if text:
2790 header.append('index %s..%s' %
2795 header.append('index %s..%s' %
2791 (gitindex(content1), gitindex(content2)))
2796 (gitindex(content1), gitindex(content2)))
2792 hunks = (None, [text]),
2797 hunks = (None, [text]),
2793 else:
2798 else:
2794 if opts.git and opts.index > 0:
2799 if opts.git and opts.index > 0:
2795 flag = flag1
2800 flag = flag1
2796 if flag is None:
2801 if flag is None:
2797 flag = flag2
2802 flag = flag2
2798 header.append('index %s..%s %s' %
2803 header.append('index %s..%s %s' %
2799 (gitindex(content1)[0:opts.index],
2804 (gitindex(content1)[0:opts.index],
2800 gitindex(content2)[0:opts.index],
2805 gitindex(content2)[0:opts.index],
2801 gitmode[flag]))
2806 gitmode[flag]))
2802
2807
2803 uheaders, hunks = mdiff.unidiff(content1, date1,
2808 uheaders, hunks = mdiff.unidiff(content1, date1,
2804 content2, date2,
2809 content2, date2,
2805 path1, path2,
2810 path1, path2,
2806 binary=binary, opts=opts)
2811 binary=binary, opts=opts)
2807 header.extend(uheaders)
2812 header.extend(uheaders)
2808 yield fctx1, fctx2, header, hunks
2813 yield fctx1, fctx2, header, hunks
2809
2814
2810 def diffstatsum(stats):
2815 def diffstatsum(stats):
2811 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
2816 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
2812 for f, a, r, b in stats:
2817 for f, a, r, b in stats:
2813 maxfile = max(maxfile, encoding.colwidth(f))
2818 maxfile = max(maxfile, encoding.colwidth(f))
2814 maxtotal = max(maxtotal, a + r)
2819 maxtotal = max(maxtotal, a + r)
2815 addtotal += a
2820 addtotal += a
2816 removetotal += r
2821 removetotal += r
2817 binary = binary or b
2822 binary = binary or b
2818
2823
2819 return maxfile, maxtotal, addtotal, removetotal, binary
2824 return maxfile, maxtotal, addtotal, removetotal, binary
2820
2825
2821 def diffstatdata(lines):
2826 def diffstatdata(lines):
2822 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
2827 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
2823
2828
2824 results = []
2829 results = []
2825 filename, adds, removes, isbinary = None, 0, 0, False
2830 filename, adds, removes, isbinary = None, 0, 0, False
2826
2831
2827 def addresult():
2832 def addresult():
2828 if filename:
2833 if filename:
2829 results.append((filename, adds, removes, isbinary))
2834 results.append((filename, adds, removes, isbinary))
2830
2835
2831 # inheader is used to track if a line is in the
2836 # inheader is used to track if a line is in the
2832 # header portion of the diff. This helps properly account
2837 # header portion of the diff. This helps properly account
2833 # for lines that start with '--' or '++'
2838 # for lines that start with '--' or '++'
2834 inheader = False
2839 inheader = False
2835
2840
2836 for line in lines:
2841 for line in lines:
2837 if line.startswith('diff'):
2842 if line.startswith('diff'):
2838 addresult()
2843 addresult()
2839 # starting a new file diff
2844 # starting a new file diff
2840 # set numbers to 0 and reset inheader
2845 # set numbers to 0 and reset inheader
2841 inheader = True
2846 inheader = True
2842 adds, removes, isbinary = 0, 0, False
2847 adds, removes, isbinary = 0, 0, False
2843 if line.startswith('diff --git a/'):
2848 if line.startswith('diff --git a/'):
2844 filename = gitre.search(line).group(2)
2849 filename = gitre.search(line).group(2)
2845 elif line.startswith('diff -r'):
2850 elif line.startswith('diff -r'):
2846 # format: "diff -r ... -r ... filename"
2851 # format: "diff -r ... -r ... filename"
2847 filename = diffre.search(line).group(1)
2852 filename = diffre.search(line).group(1)
2848 elif line.startswith('@@'):
2853 elif line.startswith('@@'):
2849 inheader = False
2854 inheader = False
2850 elif line.startswith('+') and not inheader:
2855 elif line.startswith('+') and not inheader:
2851 adds += 1
2856 adds += 1
2852 elif line.startswith('-') and not inheader:
2857 elif line.startswith('-') and not inheader:
2853 removes += 1
2858 removes += 1
2854 elif (line.startswith('GIT binary patch') or
2859 elif (line.startswith('GIT binary patch') or
2855 line.startswith('Binary file')):
2860 line.startswith('Binary file')):
2856 isbinary = True
2861 isbinary = True
2857 addresult()
2862 addresult()
2858 return results
2863 return results
2859
2864
2860 def diffstat(lines, width=80):
2865 def diffstat(lines, width=80):
2861 output = []
2866 output = []
2862 stats = diffstatdata(lines)
2867 stats = diffstatdata(lines)
2863 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
2868 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
2864
2869
2865 countwidth = len(str(maxtotal))
2870 countwidth = len(str(maxtotal))
2866 if hasbinary and countwidth < 3:
2871 if hasbinary and countwidth < 3:
2867 countwidth = 3
2872 countwidth = 3
2868 graphwidth = width - countwidth - maxname - 6
2873 graphwidth = width - countwidth - maxname - 6
2869 if graphwidth < 10:
2874 if graphwidth < 10:
2870 graphwidth = 10
2875 graphwidth = 10
2871
2876
2872 def scale(i):
2877 def scale(i):
2873 if maxtotal <= graphwidth:
2878 if maxtotal <= graphwidth:
2874 return i
2879 return i
2875 # If diffstat runs out of room it doesn't print anything,
2880 # If diffstat runs out of room it doesn't print anything,
2876 # which isn't very useful, so always print at least one + or -
2881 # which isn't very useful, so always print at least one + or -
2877 # if there were at least some changes.
2882 # if there were at least some changes.
2878 return max(i * graphwidth // maxtotal, int(bool(i)))
2883 return max(i * graphwidth // maxtotal, int(bool(i)))
2879
2884
2880 for filename, adds, removes, isbinary in stats:
2885 for filename, adds, removes, isbinary in stats:
2881 if isbinary:
2886 if isbinary:
2882 count = 'Bin'
2887 count = 'Bin'
2883 else:
2888 else:
2884 count = '%d' % (adds + removes)
2889 count = '%d' % (adds + removes)
2885 pluses = '+' * scale(adds)
2890 pluses = '+' * scale(adds)
2886 minuses = '-' * scale(removes)
2891 minuses = '-' * scale(removes)
2887 output.append(' %s%s | %*s %s%s\n' %
2892 output.append(' %s%s | %*s %s%s\n' %
2888 (filename, ' ' * (maxname - encoding.colwidth(filename)),
2893 (filename, ' ' * (maxname - encoding.colwidth(filename)),
2889 countwidth, count, pluses, minuses))
2894 countwidth, count, pluses, minuses))
2890
2895
2891 if stats:
2896 if stats:
2892 output.append(_(' %d files changed, %d insertions(+), '
2897 output.append(_(' %d files changed, %d insertions(+), '
2893 '%d deletions(-)\n')
2898 '%d deletions(-)\n')
2894 % (len(stats), totaladds, totalremoves))
2899 % (len(stats), totaladds, totalremoves))
2895
2900
2896 return ''.join(output)
2901 return ''.join(output)
2897
2902
2898 def diffstatui(*args, **kw):
2903 def diffstatui(*args, **kw):
2899 '''like diffstat(), but yields 2-tuples of (output, label) for
2904 '''like diffstat(), but yields 2-tuples of (output, label) for
2900 ui.write()
2905 ui.write()
2901 '''
2906 '''
2902
2907
2903 for line in diffstat(*args, **kw).splitlines():
2908 for line in diffstat(*args, **kw).splitlines():
2904 if line and line[-1] in '+-':
2909 if line and line[-1] in '+-':
2905 name, graph = line.rsplit(' ', 1)
2910 name, graph = line.rsplit(' ', 1)
2906 yield (name + ' ', '')
2911 yield (name + ' ', '')
2907 m = re.search(br'\++', graph)
2912 m = re.search(br'\++', graph)
2908 if m:
2913 if m:
2909 yield (m.group(0), 'diffstat.inserted')
2914 yield (m.group(0), 'diffstat.inserted')
2910 m = re.search(br'-+', graph)
2915 m = re.search(br'-+', graph)
2911 if m:
2916 if m:
2912 yield (m.group(0), 'diffstat.deleted')
2917 yield (m.group(0), 'diffstat.deleted')
2913 else:
2918 else:
2914 yield (line, '')
2919 yield (line, '')
2915 yield ('\n', '')
2920 yield ('\n', '')
General Comments 0
You need to be logged in to leave comments. Login now