##// END OF EJS Templates
status: advertise --abort instead of 'update -C .' to abort a merge...
Pulkit Goyal -
r39001:cf68e264 default
parent child Browse files
Show More
@@ -1,3271 +1,3271 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
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 hex,
16 hex,
17 nullid,
17 nullid,
18 nullrev,
18 nullrev,
19 short,
19 short,
20 )
20 )
21
21
22 from . import (
22 from . import (
23 bookmarks,
23 bookmarks,
24 changelog,
24 changelog,
25 copies,
25 copies,
26 crecord as crecordmod,
26 crecord as crecordmod,
27 dirstateguard,
27 dirstateguard,
28 encoding,
28 encoding,
29 error,
29 error,
30 formatter,
30 formatter,
31 logcmdutil,
31 logcmdutil,
32 match as matchmod,
32 match as matchmod,
33 merge as mergemod,
33 merge as mergemod,
34 mergeutil,
34 mergeutil,
35 obsolete,
35 obsolete,
36 patch,
36 patch,
37 pathutil,
37 pathutil,
38 phases,
38 phases,
39 pycompat,
39 pycompat,
40 revlog,
40 revlog,
41 rewriteutil,
41 rewriteutil,
42 scmutil,
42 scmutil,
43 smartset,
43 smartset,
44 subrepoutil,
44 subrepoutil,
45 templatekw,
45 templatekw,
46 templater,
46 templater,
47 util,
47 util,
48 vfs as vfsmod,
48 vfs as vfsmod,
49 )
49 )
50
50
51 from .utils import (
51 from .utils import (
52 dateutil,
52 dateutil,
53 stringutil,
53 stringutil,
54 )
54 )
55
55
56 stringio = util.stringio
56 stringio = util.stringio
57
57
58 # templates of common command options
58 # templates of common command options
59
59
60 dryrunopts = [
60 dryrunopts = [
61 ('n', 'dry-run', None,
61 ('n', 'dry-run', None,
62 _('do not perform actions, just print output')),
62 _('do not perform actions, just print output')),
63 ]
63 ]
64
64
65 confirmopts = [
65 confirmopts = [
66 ('', 'confirm', None,
66 ('', 'confirm', None,
67 _('ask before applying actions')),
67 _('ask before applying actions')),
68 ]
68 ]
69
69
70 remoteopts = [
70 remoteopts = [
71 ('e', 'ssh', '',
71 ('e', 'ssh', '',
72 _('specify ssh command to use'), _('CMD')),
72 _('specify ssh command to use'), _('CMD')),
73 ('', 'remotecmd', '',
73 ('', 'remotecmd', '',
74 _('specify hg command to run on the remote side'), _('CMD')),
74 _('specify hg command to run on the remote side'), _('CMD')),
75 ('', 'insecure', None,
75 ('', 'insecure', None,
76 _('do not verify server certificate (ignoring web.cacerts config)')),
76 _('do not verify server certificate (ignoring web.cacerts config)')),
77 ]
77 ]
78
78
79 walkopts = [
79 walkopts = [
80 ('I', 'include', [],
80 ('I', 'include', [],
81 _('include names matching the given patterns'), _('PATTERN')),
81 _('include names matching the given patterns'), _('PATTERN')),
82 ('X', 'exclude', [],
82 ('X', 'exclude', [],
83 _('exclude names matching the given patterns'), _('PATTERN')),
83 _('exclude names matching the given patterns'), _('PATTERN')),
84 ]
84 ]
85
85
86 commitopts = [
86 commitopts = [
87 ('m', 'message', '',
87 ('m', 'message', '',
88 _('use text as commit message'), _('TEXT')),
88 _('use text as commit message'), _('TEXT')),
89 ('l', 'logfile', '',
89 ('l', 'logfile', '',
90 _('read commit message from file'), _('FILE')),
90 _('read commit message from file'), _('FILE')),
91 ]
91 ]
92
92
93 commitopts2 = [
93 commitopts2 = [
94 ('d', 'date', '',
94 ('d', 'date', '',
95 _('record the specified date as commit date'), _('DATE')),
95 _('record the specified date as commit date'), _('DATE')),
96 ('u', 'user', '',
96 ('u', 'user', '',
97 _('record the specified user as committer'), _('USER')),
97 _('record the specified user as committer'), _('USER')),
98 ]
98 ]
99
99
100 # hidden for now
100 # hidden for now
101 formatteropts = [
101 formatteropts = [
102 ('T', 'template', '',
102 ('T', 'template', '',
103 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
103 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
104 ]
104 ]
105
105
106 templateopts = [
106 templateopts = [
107 ('', 'style', '',
107 ('', 'style', '',
108 _('display using template map file (DEPRECATED)'), _('STYLE')),
108 _('display using template map file (DEPRECATED)'), _('STYLE')),
109 ('T', 'template', '',
109 ('T', 'template', '',
110 _('display with template'), _('TEMPLATE')),
110 _('display with template'), _('TEMPLATE')),
111 ]
111 ]
112
112
113 logopts = [
113 logopts = [
114 ('p', 'patch', None, _('show patch')),
114 ('p', 'patch', None, _('show patch')),
115 ('g', 'git', None, _('use git extended diff format')),
115 ('g', 'git', None, _('use git extended diff format')),
116 ('l', 'limit', '',
116 ('l', 'limit', '',
117 _('limit number of changes displayed'), _('NUM')),
117 _('limit number of changes displayed'), _('NUM')),
118 ('M', 'no-merges', None, _('do not show merges')),
118 ('M', 'no-merges', None, _('do not show merges')),
119 ('', 'stat', None, _('output diffstat-style summary of changes')),
119 ('', 'stat', None, _('output diffstat-style summary of changes')),
120 ('G', 'graph', None, _("show the revision DAG")),
120 ('G', 'graph', None, _("show the revision DAG")),
121 ] + templateopts
121 ] + templateopts
122
122
123 diffopts = [
123 diffopts = [
124 ('a', 'text', None, _('treat all files as text')),
124 ('a', 'text', None, _('treat all files as text')),
125 ('g', 'git', None, _('use git extended diff format')),
125 ('g', 'git', None, _('use git extended diff format')),
126 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
126 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
127 ('', 'nodates', None, _('omit dates from diff headers'))
127 ('', 'nodates', None, _('omit dates from diff headers'))
128 ]
128 ]
129
129
130 diffwsopts = [
130 diffwsopts = [
131 ('w', 'ignore-all-space', None,
131 ('w', 'ignore-all-space', None,
132 _('ignore white space when comparing lines')),
132 _('ignore white space when comparing lines')),
133 ('b', 'ignore-space-change', None,
133 ('b', 'ignore-space-change', None,
134 _('ignore changes in the amount of white space')),
134 _('ignore changes in the amount of white space')),
135 ('B', 'ignore-blank-lines', None,
135 ('B', 'ignore-blank-lines', None,
136 _('ignore changes whose lines are all blank')),
136 _('ignore changes whose lines are all blank')),
137 ('Z', 'ignore-space-at-eol', None,
137 ('Z', 'ignore-space-at-eol', None,
138 _('ignore changes in whitespace at EOL')),
138 _('ignore changes in whitespace at EOL')),
139 ]
139 ]
140
140
141 diffopts2 = [
141 diffopts2 = [
142 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
142 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
143 ('p', 'show-function', None, _('show which function each change is in')),
143 ('p', 'show-function', None, _('show which function each change is in')),
144 ('', 'reverse', None, _('produce a diff that undoes the changes')),
144 ('', 'reverse', None, _('produce a diff that undoes the changes')),
145 ] + diffwsopts + [
145 ] + diffwsopts + [
146 ('U', 'unified', '',
146 ('U', 'unified', '',
147 _('number of lines of context to show'), _('NUM')),
147 _('number of lines of context to show'), _('NUM')),
148 ('', 'stat', None, _('output diffstat-style summary of changes')),
148 ('', 'stat', None, _('output diffstat-style summary of changes')),
149 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
149 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
150 ]
150 ]
151
151
152 mergetoolopts = [
152 mergetoolopts = [
153 ('t', 'tool', '', _('specify merge tool')),
153 ('t', 'tool', '', _('specify merge tool')),
154 ]
154 ]
155
155
156 similarityopts = [
156 similarityopts = [
157 ('s', 'similarity', '',
157 ('s', 'similarity', '',
158 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
158 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
159 ]
159 ]
160
160
161 subrepoopts = [
161 subrepoopts = [
162 ('S', 'subrepos', None,
162 ('S', 'subrepos', None,
163 _('recurse into subrepositories'))
163 _('recurse into subrepositories'))
164 ]
164 ]
165
165
166 debugrevlogopts = [
166 debugrevlogopts = [
167 ('c', 'changelog', False, _('open changelog')),
167 ('c', 'changelog', False, _('open changelog')),
168 ('m', 'manifest', False, _('open manifest')),
168 ('m', 'manifest', False, _('open manifest')),
169 ('', 'dir', '', _('open directory manifest')),
169 ('', 'dir', '', _('open directory manifest')),
170 ]
170 ]
171
171
172 # special string such that everything below this line will be ingored in the
172 # special string such that everything below this line will be ingored in the
173 # editor text
173 # editor text
174 _linebelow = "^HG: ------------------------ >8 ------------------------$"
174 _linebelow = "^HG: ------------------------ >8 ------------------------$"
175
175
176 def ishunk(x):
176 def ishunk(x):
177 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
177 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
178 return isinstance(x, hunkclasses)
178 return isinstance(x, hunkclasses)
179
179
180 def newandmodified(chunks, originalchunks):
180 def newandmodified(chunks, originalchunks):
181 newlyaddedandmodifiedfiles = set()
181 newlyaddedandmodifiedfiles = set()
182 for chunk in chunks:
182 for chunk in chunks:
183 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
183 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
184 originalchunks:
184 originalchunks:
185 newlyaddedandmodifiedfiles.add(chunk.header.filename())
185 newlyaddedandmodifiedfiles.add(chunk.header.filename())
186 return newlyaddedandmodifiedfiles
186 return newlyaddedandmodifiedfiles
187
187
188 def parsealiases(cmd):
188 def parsealiases(cmd):
189 return cmd.lstrip("^").split("|")
189 return cmd.lstrip("^").split("|")
190
190
191 def setupwrapcolorwrite(ui):
191 def setupwrapcolorwrite(ui):
192 # wrap ui.write so diff output can be labeled/colorized
192 # wrap ui.write so diff output can be labeled/colorized
193 def wrapwrite(orig, *args, **kw):
193 def wrapwrite(orig, *args, **kw):
194 label = kw.pop(r'label', '')
194 label = kw.pop(r'label', '')
195 for chunk, l in patch.difflabel(lambda: args):
195 for chunk, l in patch.difflabel(lambda: args):
196 orig(chunk, label=label + l)
196 orig(chunk, label=label + l)
197
197
198 oldwrite = ui.write
198 oldwrite = ui.write
199 def wrap(*args, **kwargs):
199 def wrap(*args, **kwargs):
200 return wrapwrite(oldwrite, *args, **kwargs)
200 return wrapwrite(oldwrite, *args, **kwargs)
201 setattr(ui, 'write', wrap)
201 setattr(ui, 'write', wrap)
202 return oldwrite
202 return oldwrite
203
203
204 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
204 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
205 try:
205 try:
206 if usecurses:
206 if usecurses:
207 if testfile:
207 if testfile:
208 recordfn = crecordmod.testdecorator(
208 recordfn = crecordmod.testdecorator(
209 testfile, crecordmod.testchunkselector)
209 testfile, crecordmod.testchunkselector)
210 else:
210 else:
211 recordfn = crecordmod.chunkselector
211 recordfn = crecordmod.chunkselector
212
212
213 return crecordmod.filterpatch(ui, originalhunks, recordfn,
213 return crecordmod.filterpatch(ui, originalhunks, recordfn,
214 operation)
214 operation)
215 except crecordmod.fallbackerror as e:
215 except crecordmod.fallbackerror as e:
216 ui.warn('%s\n' % e.message)
216 ui.warn('%s\n' % e.message)
217 ui.warn(_('falling back to text mode\n'))
217 ui.warn(_('falling back to text mode\n'))
218
218
219 return patch.filterpatch(ui, originalhunks, operation)
219 return patch.filterpatch(ui, originalhunks, operation)
220
220
221 def recordfilter(ui, originalhunks, operation=None):
221 def recordfilter(ui, originalhunks, operation=None):
222 """ Prompts the user to filter the originalhunks and return a list of
222 """ Prompts the user to filter the originalhunks and return a list of
223 selected hunks.
223 selected hunks.
224 *operation* is used for to build ui messages to indicate the user what
224 *operation* is used for to build ui messages to indicate the user what
225 kind of filtering they are doing: reverting, committing, shelving, etc.
225 kind of filtering they are doing: reverting, committing, shelving, etc.
226 (see patch.filterpatch).
226 (see patch.filterpatch).
227 """
227 """
228 usecurses = crecordmod.checkcurses(ui)
228 usecurses = crecordmod.checkcurses(ui)
229 testfile = ui.config('experimental', 'crecordtest')
229 testfile = ui.config('experimental', 'crecordtest')
230 oldwrite = setupwrapcolorwrite(ui)
230 oldwrite = setupwrapcolorwrite(ui)
231 try:
231 try:
232 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
232 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
233 testfile, operation)
233 testfile, operation)
234 finally:
234 finally:
235 ui.write = oldwrite
235 ui.write = oldwrite
236 return newchunks, newopts
236 return newchunks, newopts
237
237
238 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
238 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
239 filterfn, *pats, **opts):
239 filterfn, *pats, **opts):
240 opts = pycompat.byteskwargs(opts)
240 opts = pycompat.byteskwargs(opts)
241 if not ui.interactive():
241 if not ui.interactive():
242 if cmdsuggest:
242 if cmdsuggest:
243 msg = _('running non-interactively, use %s instead') % cmdsuggest
243 msg = _('running non-interactively, use %s instead') % cmdsuggest
244 else:
244 else:
245 msg = _('running non-interactively')
245 msg = _('running non-interactively')
246 raise error.Abort(msg)
246 raise error.Abort(msg)
247
247
248 # make sure username is set before going interactive
248 # make sure username is set before going interactive
249 if not opts.get('user'):
249 if not opts.get('user'):
250 ui.username() # raise exception, username not provided
250 ui.username() # raise exception, username not provided
251
251
252 def recordfunc(ui, repo, message, match, opts):
252 def recordfunc(ui, repo, message, match, opts):
253 """This is generic record driver.
253 """This is generic record driver.
254
254
255 Its job is to interactively filter local changes, and
255 Its job is to interactively filter local changes, and
256 accordingly prepare working directory into a state in which the
256 accordingly prepare working directory into a state in which the
257 job can be delegated to a non-interactive commit command such as
257 job can be delegated to a non-interactive commit command such as
258 'commit' or 'qrefresh'.
258 'commit' or 'qrefresh'.
259
259
260 After the actual job is done by non-interactive command, the
260 After the actual job is done by non-interactive command, the
261 working directory is restored to its original state.
261 working directory is restored to its original state.
262
262
263 In the end we'll record interesting changes, and everything else
263 In the end we'll record interesting changes, and everything else
264 will be left in place, so the user can continue working.
264 will be left in place, so the user can continue working.
265 """
265 """
266
266
267 checkunfinished(repo, commit=True)
267 checkunfinished(repo, commit=True)
268 wctx = repo[None]
268 wctx = repo[None]
269 merge = len(wctx.parents()) > 1
269 merge = len(wctx.parents()) > 1
270 if merge:
270 if merge:
271 raise error.Abort(_('cannot partially commit a merge '
271 raise error.Abort(_('cannot partially commit a merge '
272 '(use "hg commit" instead)'))
272 '(use "hg commit" instead)'))
273
273
274 def fail(f, msg):
274 def fail(f, msg):
275 raise error.Abort('%s: %s' % (f, msg))
275 raise error.Abort('%s: %s' % (f, msg))
276
276
277 force = opts.get('force')
277 force = opts.get('force')
278 if not force:
278 if not force:
279 vdirs = []
279 vdirs = []
280 match.explicitdir = vdirs.append
280 match.explicitdir = vdirs.append
281 match.bad = fail
281 match.bad = fail
282
282
283 status = repo.status(match=match)
283 status = repo.status(match=match)
284 if not force:
284 if not force:
285 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
285 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
286 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
286 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
287 diffopts.nodates = True
287 diffopts.nodates = True
288 diffopts.git = True
288 diffopts.git = True
289 diffopts.showfunc = True
289 diffopts.showfunc = True
290 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
290 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
291 originalchunks = patch.parsepatch(originaldiff)
291 originalchunks = patch.parsepatch(originaldiff)
292
292
293 # 1. filter patch, since we are intending to apply subset of it
293 # 1. filter patch, since we are intending to apply subset of it
294 try:
294 try:
295 chunks, newopts = filterfn(ui, originalchunks)
295 chunks, newopts = filterfn(ui, originalchunks)
296 except error.PatchError as err:
296 except error.PatchError as err:
297 raise error.Abort(_('error parsing patch: %s') % err)
297 raise error.Abort(_('error parsing patch: %s') % err)
298 opts.update(newopts)
298 opts.update(newopts)
299
299
300 # We need to keep a backup of files that have been newly added and
300 # We need to keep a backup of files that have been newly added and
301 # modified during the recording process because there is a previous
301 # modified during the recording process because there is a previous
302 # version without the edit in the workdir
302 # version without the edit in the workdir
303 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
303 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
304 contenders = set()
304 contenders = set()
305 for h in chunks:
305 for h in chunks:
306 try:
306 try:
307 contenders.update(set(h.files()))
307 contenders.update(set(h.files()))
308 except AttributeError:
308 except AttributeError:
309 pass
309 pass
310
310
311 changed = status.modified + status.added + status.removed
311 changed = status.modified + status.added + status.removed
312 newfiles = [f for f in changed if f in contenders]
312 newfiles = [f for f in changed if f in contenders]
313 if not newfiles:
313 if not newfiles:
314 ui.status(_('no changes to record\n'))
314 ui.status(_('no changes to record\n'))
315 return 0
315 return 0
316
316
317 modified = set(status.modified)
317 modified = set(status.modified)
318
318
319 # 2. backup changed files, so we can restore them in the end
319 # 2. backup changed files, so we can restore them in the end
320
320
321 if backupall:
321 if backupall:
322 tobackup = changed
322 tobackup = changed
323 else:
323 else:
324 tobackup = [f for f in newfiles if f in modified or f in \
324 tobackup = [f for f in newfiles if f in modified or f in \
325 newlyaddedandmodifiedfiles]
325 newlyaddedandmodifiedfiles]
326 backups = {}
326 backups = {}
327 if tobackup:
327 if tobackup:
328 backupdir = repo.vfs.join('record-backups')
328 backupdir = repo.vfs.join('record-backups')
329 try:
329 try:
330 os.mkdir(backupdir)
330 os.mkdir(backupdir)
331 except OSError as err:
331 except OSError as err:
332 if err.errno != errno.EEXIST:
332 if err.errno != errno.EEXIST:
333 raise
333 raise
334 try:
334 try:
335 # backup continues
335 # backup continues
336 for f in tobackup:
336 for f in tobackup:
337 fd, tmpname = pycompat.mkstemp(prefix=f.replace('/', '_') + '.',
337 fd, tmpname = pycompat.mkstemp(prefix=f.replace('/', '_') + '.',
338 dir=backupdir)
338 dir=backupdir)
339 os.close(fd)
339 os.close(fd)
340 ui.debug('backup %r as %r\n' % (f, tmpname))
340 ui.debug('backup %r as %r\n' % (f, tmpname))
341 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
341 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
342 backups[f] = tmpname
342 backups[f] = tmpname
343
343
344 fp = stringio()
344 fp = stringio()
345 for c in chunks:
345 for c in chunks:
346 fname = c.filename()
346 fname = c.filename()
347 if fname in backups:
347 if fname in backups:
348 c.write(fp)
348 c.write(fp)
349 dopatch = fp.tell()
349 dopatch = fp.tell()
350 fp.seek(0)
350 fp.seek(0)
351
351
352 # 2.5 optionally review / modify patch in text editor
352 # 2.5 optionally review / modify patch in text editor
353 if opts.get('review', False):
353 if opts.get('review', False):
354 patchtext = (crecordmod.diffhelptext
354 patchtext = (crecordmod.diffhelptext
355 + crecordmod.patchhelptext
355 + crecordmod.patchhelptext
356 + fp.read())
356 + fp.read())
357 reviewedpatch = ui.edit(patchtext, "",
357 reviewedpatch = ui.edit(patchtext, "",
358 action="diff",
358 action="diff",
359 repopath=repo.path)
359 repopath=repo.path)
360 fp.truncate(0)
360 fp.truncate(0)
361 fp.write(reviewedpatch)
361 fp.write(reviewedpatch)
362 fp.seek(0)
362 fp.seek(0)
363
363
364 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
364 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
365 # 3a. apply filtered patch to clean repo (clean)
365 # 3a. apply filtered patch to clean repo (clean)
366 if backups:
366 if backups:
367 # Equivalent to hg.revert
367 # Equivalent to hg.revert
368 m = scmutil.matchfiles(repo, backups.keys())
368 m = scmutil.matchfiles(repo, backups.keys())
369 mergemod.update(repo, repo.dirstate.p1(),
369 mergemod.update(repo, repo.dirstate.p1(),
370 False, True, matcher=m)
370 False, True, matcher=m)
371
371
372 # 3b. (apply)
372 # 3b. (apply)
373 if dopatch:
373 if dopatch:
374 try:
374 try:
375 ui.debug('applying patch\n')
375 ui.debug('applying patch\n')
376 ui.debug(fp.getvalue())
376 ui.debug(fp.getvalue())
377 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
377 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
378 except error.PatchError as err:
378 except error.PatchError as err:
379 raise error.Abort(pycompat.bytestr(err))
379 raise error.Abort(pycompat.bytestr(err))
380 del fp
380 del fp
381
381
382 # 4. We prepared working directory according to filtered
382 # 4. We prepared working directory according to filtered
383 # patch. Now is the time to delegate the job to
383 # patch. Now is the time to delegate the job to
384 # commit/qrefresh or the like!
384 # commit/qrefresh or the like!
385
385
386 # Make all of the pathnames absolute.
386 # Make all of the pathnames absolute.
387 newfiles = [repo.wjoin(nf) for nf in newfiles]
387 newfiles = [repo.wjoin(nf) for nf in newfiles]
388 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
388 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
389 finally:
389 finally:
390 # 5. finally restore backed-up files
390 # 5. finally restore backed-up files
391 try:
391 try:
392 dirstate = repo.dirstate
392 dirstate = repo.dirstate
393 for realname, tmpname in backups.iteritems():
393 for realname, tmpname in backups.iteritems():
394 ui.debug('restoring %r to %r\n' % (tmpname, realname))
394 ui.debug('restoring %r to %r\n' % (tmpname, realname))
395
395
396 if dirstate[realname] == 'n':
396 if dirstate[realname] == 'n':
397 # without normallookup, restoring timestamp
397 # without normallookup, restoring timestamp
398 # may cause partially committed files
398 # may cause partially committed files
399 # to be treated as unmodified
399 # to be treated as unmodified
400 dirstate.normallookup(realname)
400 dirstate.normallookup(realname)
401
401
402 # copystat=True here and above are a hack to trick any
402 # copystat=True here and above are a hack to trick any
403 # editors that have f open that we haven't modified them.
403 # editors that have f open that we haven't modified them.
404 #
404 #
405 # Also note that this racy as an editor could notice the
405 # Also note that this racy as an editor could notice the
406 # file's mtime before we've finished writing it.
406 # file's mtime before we've finished writing it.
407 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
407 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
408 os.unlink(tmpname)
408 os.unlink(tmpname)
409 if tobackup:
409 if tobackup:
410 os.rmdir(backupdir)
410 os.rmdir(backupdir)
411 except OSError:
411 except OSError:
412 pass
412 pass
413
413
414 def recordinwlock(ui, repo, message, match, opts):
414 def recordinwlock(ui, repo, message, match, opts):
415 with repo.wlock():
415 with repo.wlock():
416 return recordfunc(ui, repo, message, match, opts)
416 return recordfunc(ui, repo, message, match, opts)
417
417
418 return commit(ui, repo, recordinwlock, pats, opts)
418 return commit(ui, repo, recordinwlock, pats, opts)
419
419
420 class dirnode(object):
420 class dirnode(object):
421 """
421 """
422 Represent a directory in user working copy with information required for
422 Represent a directory in user working copy with information required for
423 the purpose of tersing its status.
423 the purpose of tersing its status.
424
424
425 path is the path to the directory, without a trailing '/'
425 path is the path to the directory, without a trailing '/'
426
426
427 statuses is a set of statuses of all files in this directory (this includes
427 statuses is a set of statuses of all files in this directory (this includes
428 all the files in all the subdirectories too)
428 all the files in all the subdirectories too)
429
429
430 files is a list of files which are direct child of this directory
430 files is a list of files which are direct child of this directory
431
431
432 subdirs is a dictionary of sub-directory name as the key and it's own
432 subdirs is a dictionary of sub-directory name as the key and it's own
433 dirnode object as the value
433 dirnode object as the value
434 """
434 """
435
435
436 def __init__(self, dirpath):
436 def __init__(self, dirpath):
437 self.path = dirpath
437 self.path = dirpath
438 self.statuses = set([])
438 self.statuses = set([])
439 self.files = []
439 self.files = []
440 self.subdirs = {}
440 self.subdirs = {}
441
441
442 def _addfileindir(self, filename, status):
442 def _addfileindir(self, filename, status):
443 """Add a file in this directory as a direct child."""
443 """Add a file in this directory as a direct child."""
444 self.files.append((filename, status))
444 self.files.append((filename, status))
445
445
446 def addfile(self, filename, status):
446 def addfile(self, filename, status):
447 """
447 """
448 Add a file to this directory or to its direct parent directory.
448 Add a file to this directory or to its direct parent directory.
449
449
450 If the file is not direct child of this directory, we traverse to the
450 If the file is not direct child of this directory, we traverse to the
451 directory of which this file is a direct child of and add the file
451 directory of which this file is a direct child of and add the file
452 there.
452 there.
453 """
453 """
454
454
455 # the filename contains a path separator, it means it's not the direct
455 # the filename contains a path separator, it means it's not the direct
456 # child of this directory
456 # child of this directory
457 if '/' in filename:
457 if '/' in filename:
458 subdir, filep = filename.split('/', 1)
458 subdir, filep = filename.split('/', 1)
459
459
460 # does the dirnode object for subdir exists
460 # does the dirnode object for subdir exists
461 if subdir not in self.subdirs:
461 if subdir not in self.subdirs:
462 subdirpath = pathutil.join(self.path, subdir)
462 subdirpath = pathutil.join(self.path, subdir)
463 self.subdirs[subdir] = dirnode(subdirpath)
463 self.subdirs[subdir] = dirnode(subdirpath)
464
464
465 # try adding the file in subdir
465 # try adding the file in subdir
466 self.subdirs[subdir].addfile(filep, status)
466 self.subdirs[subdir].addfile(filep, status)
467
467
468 else:
468 else:
469 self._addfileindir(filename, status)
469 self._addfileindir(filename, status)
470
470
471 if status not in self.statuses:
471 if status not in self.statuses:
472 self.statuses.add(status)
472 self.statuses.add(status)
473
473
474 def iterfilepaths(self):
474 def iterfilepaths(self):
475 """Yield (status, path) for files directly under this directory."""
475 """Yield (status, path) for files directly under this directory."""
476 for f, st in self.files:
476 for f, st in self.files:
477 yield st, pathutil.join(self.path, f)
477 yield st, pathutil.join(self.path, f)
478
478
479 def tersewalk(self, terseargs):
479 def tersewalk(self, terseargs):
480 """
480 """
481 Yield (status, path) obtained by processing the status of this
481 Yield (status, path) obtained by processing the status of this
482 dirnode.
482 dirnode.
483
483
484 terseargs is the string of arguments passed by the user with `--terse`
484 terseargs is the string of arguments passed by the user with `--terse`
485 flag.
485 flag.
486
486
487 Following are the cases which can happen:
487 Following are the cases which can happen:
488
488
489 1) All the files in the directory (including all the files in its
489 1) All the files in the directory (including all the files in its
490 subdirectories) share the same status and the user has asked us to terse
490 subdirectories) share the same status and the user has asked us to terse
491 that status. -> yield (status, dirpath). dirpath will end in '/'.
491 that status. -> yield (status, dirpath). dirpath will end in '/'.
492
492
493 2) Otherwise, we do following:
493 2) Otherwise, we do following:
494
494
495 a) Yield (status, filepath) for all the files which are in this
495 a) Yield (status, filepath) for all the files which are in this
496 directory (only the ones in this directory, not the subdirs)
496 directory (only the ones in this directory, not the subdirs)
497
497
498 b) Recurse the function on all the subdirectories of this
498 b) Recurse the function on all the subdirectories of this
499 directory
499 directory
500 """
500 """
501
501
502 if len(self.statuses) == 1:
502 if len(self.statuses) == 1:
503 onlyst = self.statuses.pop()
503 onlyst = self.statuses.pop()
504
504
505 # Making sure we terse only when the status abbreviation is
505 # Making sure we terse only when the status abbreviation is
506 # passed as terse argument
506 # passed as terse argument
507 if onlyst in terseargs:
507 if onlyst in terseargs:
508 yield onlyst, self.path + '/'
508 yield onlyst, self.path + '/'
509 return
509 return
510
510
511 # add the files to status list
511 # add the files to status list
512 for st, fpath in self.iterfilepaths():
512 for st, fpath in self.iterfilepaths():
513 yield st, fpath
513 yield st, fpath
514
514
515 #recurse on the subdirs
515 #recurse on the subdirs
516 for dirobj in self.subdirs.values():
516 for dirobj in self.subdirs.values():
517 for st, fpath in dirobj.tersewalk(terseargs):
517 for st, fpath in dirobj.tersewalk(terseargs):
518 yield st, fpath
518 yield st, fpath
519
519
520 def tersedir(statuslist, terseargs):
520 def tersedir(statuslist, terseargs):
521 """
521 """
522 Terse the status if all the files in a directory shares the same status.
522 Terse the status if all the files in a directory shares the same status.
523
523
524 statuslist is scmutil.status() object which contains a list of files for
524 statuslist is scmutil.status() object which contains a list of files for
525 each status.
525 each status.
526 terseargs is string which is passed by the user as the argument to `--terse`
526 terseargs is string which is passed by the user as the argument to `--terse`
527 flag.
527 flag.
528
528
529 The function makes a tree of objects of dirnode class, and at each node it
529 The function makes a tree of objects of dirnode class, and at each node it
530 stores the information required to know whether we can terse a certain
530 stores the information required to know whether we can terse a certain
531 directory or not.
531 directory or not.
532 """
532 """
533 # the order matters here as that is used to produce final list
533 # the order matters here as that is used to produce final list
534 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
534 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
535
535
536 # checking the argument validity
536 # checking the argument validity
537 for s in pycompat.bytestr(terseargs):
537 for s in pycompat.bytestr(terseargs):
538 if s not in allst:
538 if s not in allst:
539 raise error.Abort(_("'%s' not recognized") % s)
539 raise error.Abort(_("'%s' not recognized") % s)
540
540
541 # creating a dirnode object for the root of the repo
541 # creating a dirnode object for the root of the repo
542 rootobj = dirnode('')
542 rootobj = dirnode('')
543 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
543 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
544 'ignored', 'removed')
544 'ignored', 'removed')
545
545
546 tersedict = {}
546 tersedict = {}
547 for attrname in pstatus:
547 for attrname in pstatus:
548 statuschar = attrname[0:1]
548 statuschar = attrname[0:1]
549 for f in getattr(statuslist, attrname):
549 for f in getattr(statuslist, attrname):
550 rootobj.addfile(f, statuschar)
550 rootobj.addfile(f, statuschar)
551 tersedict[statuschar] = []
551 tersedict[statuschar] = []
552
552
553 # we won't be tersing the root dir, so add files in it
553 # we won't be tersing the root dir, so add files in it
554 for st, fpath in rootobj.iterfilepaths():
554 for st, fpath in rootobj.iterfilepaths():
555 tersedict[st].append(fpath)
555 tersedict[st].append(fpath)
556
556
557 # process each sub-directory and build tersedict
557 # process each sub-directory and build tersedict
558 for subdir in rootobj.subdirs.values():
558 for subdir in rootobj.subdirs.values():
559 for st, f in subdir.tersewalk(terseargs):
559 for st, f in subdir.tersewalk(terseargs):
560 tersedict[st].append(f)
560 tersedict[st].append(f)
561
561
562 tersedlist = []
562 tersedlist = []
563 for st in allst:
563 for st in allst:
564 tersedict[st].sort()
564 tersedict[st].sort()
565 tersedlist.append(tersedict[st])
565 tersedlist.append(tersedict[st])
566
566
567 return tersedlist
567 return tersedlist
568
568
569 def _commentlines(raw):
569 def _commentlines(raw):
570 '''Surround lineswith a comment char and a new line'''
570 '''Surround lineswith a comment char and a new line'''
571 lines = raw.splitlines()
571 lines = raw.splitlines()
572 commentedlines = ['# %s' % line for line in lines]
572 commentedlines = ['# %s' % line for line in lines]
573 return '\n'.join(commentedlines) + '\n'
573 return '\n'.join(commentedlines) + '\n'
574
574
575 def _conflictsmsg(repo):
575 def _conflictsmsg(repo):
576 mergestate = mergemod.mergestate.read(repo)
576 mergestate = mergemod.mergestate.read(repo)
577 if not mergestate.active():
577 if not mergestate.active():
578 return
578 return
579
579
580 m = scmutil.match(repo[None])
580 m = scmutil.match(repo[None])
581 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
581 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
582 if unresolvedlist:
582 if unresolvedlist:
583 mergeliststr = '\n'.join(
583 mergeliststr = '\n'.join(
584 [' %s' % util.pathto(repo.root, pycompat.getcwd(), path)
584 [' %s' % util.pathto(repo.root, pycompat.getcwd(), path)
585 for path in unresolvedlist])
585 for path in unresolvedlist])
586 msg = _('''Unresolved merge conflicts:
586 msg = _('''Unresolved merge conflicts:
587
587
588 %s
588 %s
589
589
590 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
590 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
591 else:
591 else:
592 msg = _('No unresolved merge conflicts.')
592 msg = _('No unresolved merge conflicts.')
593
593
594 return _commentlines(msg)
594 return _commentlines(msg)
595
595
596 def _helpmessage(continuecmd, abortcmd):
596 def _helpmessage(continuecmd, abortcmd):
597 msg = _('To continue: %s\n'
597 msg = _('To continue: %s\n'
598 'To abort: %s') % (continuecmd, abortcmd)
598 'To abort: %s') % (continuecmd, abortcmd)
599 return _commentlines(msg)
599 return _commentlines(msg)
600
600
601 def _rebasemsg():
601 def _rebasemsg():
602 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
602 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
603
603
604 def _histeditmsg():
604 def _histeditmsg():
605 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
605 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
606
606
607 def _unshelvemsg():
607 def _unshelvemsg():
608 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
608 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
609
609
610 def _updatecleanmsg(dest=None):
610 def _updatecleanmsg(dest=None):
611 warning = _('warning: this will discard uncommitted changes')
611 warning = _('warning: this will discard uncommitted changes')
612 return 'hg update --clean %s (%s)' % (dest or '.', warning)
612 return 'hg update --clean %s (%s)' % (dest or '.', warning)
613
613
614 def _graftmsg():
614 def _graftmsg():
615 # tweakdefaults requires `update` to have a rev hence the `.`
615 # tweakdefaults requires `update` to have a rev hence the `.`
616 return _helpmessage('hg graft --continue', _updatecleanmsg())
616 return _helpmessage('hg graft --continue', _updatecleanmsg())
617
617
618 def _mergemsg():
618 def _mergemsg():
619 # tweakdefaults requires `update` to have a rev hence the `.`
619 # tweakdefaults requires `update` to have a rev hence the `.`
620 return _helpmessage('hg commit', _updatecleanmsg())
620 return _helpmessage('hg commit', 'hg merge --abort')
621
621
622 def _bisectmsg():
622 def _bisectmsg():
623 msg = _('To mark the changeset good: hg bisect --good\n'
623 msg = _('To mark the changeset good: hg bisect --good\n'
624 'To mark the changeset bad: hg bisect --bad\n'
624 'To mark the changeset bad: hg bisect --bad\n'
625 'To abort: hg bisect --reset\n')
625 'To abort: hg bisect --reset\n')
626 return _commentlines(msg)
626 return _commentlines(msg)
627
627
628 def fileexistspredicate(filename):
628 def fileexistspredicate(filename):
629 return lambda repo: repo.vfs.exists(filename)
629 return lambda repo: repo.vfs.exists(filename)
630
630
631 def _mergepredicate(repo):
631 def _mergepredicate(repo):
632 return len(repo[None].parents()) > 1
632 return len(repo[None].parents()) > 1
633
633
634 STATES = (
634 STATES = (
635 # (state, predicate to detect states, helpful message function)
635 # (state, predicate to detect states, helpful message function)
636 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
636 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
637 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
637 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
638 ('graft', fileexistspredicate('graftstate'), _graftmsg),
638 ('graft', fileexistspredicate('graftstate'), _graftmsg),
639 ('unshelve', fileexistspredicate('shelvedstate'), _unshelvemsg),
639 ('unshelve', fileexistspredicate('shelvedstate'), _unshelvemsg),
640 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
640 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
641 # The merge state is part of a list that will be iterated over.
641 # The merge state is part of a list that will be iterated over.
642 # They need to be last because some of the other unfinished states may also
642 # They need to be last because some of the other unfinished states may also
643 # be in a merge or update state (eg. rebase, histedit, graft, etc).
643 # be in a merge or update state (eg. rebase, histedit, graft, etc).
644 # We want those to have priority.
644 # We want those to have priority.
645 ('merge', _mergepredicate, _mergemsg),
645 ('merge', _mergepredicate, _mergemsg),
646 )
646 )
647
647
648 def _getrepostate(repo):
648 def _getrepostate(repo):
649 # experimental config: commands.status.skipstates
649 # experimental config: commands.status.skipstates
650 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
650 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
651 for state, statedetectionpredicate, msgfn in STATES:
651 for state, statedetectionpredicate, msgfn in STATES:
652 if state in skip:
652 if state in skip:
653 continue
653 continue
654 if statedetectionpredicate(repo):
654 if statedetectionpredicate(repo):
655 return (state, statedetectionpredicate, msgfn)
655 return (state, statedetectionpredicate, msgfn)
656
656
657 def morestatus(repo, fm):
657 def morestatus(repo, fm):
658 statetuple = _getrepostate(repo)
658 statetuple = _getrepostate(repo)
659 label = 'status.morestatus'
659 label = 'status.morestatus'
660 if statetuple:
660 if statetuple:
661 fm.startitem()
661 fm.startitem()
662 state, statedetectionpredicate, helpfulmsg = statetuple
662 state, statedetectionpredicate, helpfulmsg = statetuple
663 statemsg = _('The repository is in an unfinished *%s* state.') % state
663 statemsg = _('The repository is in an unfinished *%s* state.') % state
664 fm.write('statemsg', '%s\n', _commentlines(statemsg), label=label)
664 fm.write('statemsg', '%s\n', _commentlines(statemsg), label=label)
665 conmsg = _conflictsmsg(repo)
665 conmsg = _conflictsmsg(repo)
666 if conmsg:
666 if conmsg:
667 fm.write('conflictsmsg', '%s\n', conmsg, label=label)
667 fm.write('conflictsmsg', '%s\n', conmsg, label=label)
668 if helpfulmsg:
668 if helpfulmsg:
669 helpmsg = helpfulmsg()
669 helpmsg = helpfulmsg()
670 fm.write('helpmsg', '%s\n', helpmsg, label=label)
670 fm.write('helpmsg', '%s\n', helpmsg, label=label)
671
671
672 def findpossible(cmd, table, strict=False):
672 def findpossible(cmd, table, strict=False):
673 """
673 """
674 Return cmd -> (aliases, command table entry)
674 Return cmd -> (aliases, command table entry)
675 for each matching command.
675 for each matching command.
676 Return debug commands (or their aliases) only if no normal command matches.
676 Return debug commands (or their aliases) only if no normal command matches.
677 """
677 """
678 choice = {}
678 choice = {}
679 debugchoice = {}
679 debugchoice = {}
680
680
681 if cmd in table:
681 if cmd in table:
682 # short-circuit exact matches, "log" alias beats "^log|history"
682 # short-circuit exact matches, "log" alias beats "^log|history"
683 keys = [cmd]
683 keys = [cmd]
684 else:
684 else:
685 keys = table.keys()
685 keys = table.keys()
686
686
687 allcmds = []
687 allcmds = []
688 for e in keys:
688 for e in keys:
689 aliases = parsealiases(e)
689 aliases = parsealiases(e)
690 allcmds.extend(aliases)
690 allcmds.extend(aliases)
691 found = None
691 found = None
692 if cmd in aliases:
692 if cmd in aliases:
693 found = cmd
693 found = cmd
694 elif not strict:
694 elif not strict:
695 for a in aliases:
695 for a in aliases:
696 if a.startswith(cmd):
696 if a.startswith(cmd):
697 found = a
697 found = a
698 break
698 break
699 if found is not None:
699 if found is not None:
700 if aliases[0].startswith("debug") or found.startswith("debug"):
700 if aliases[0].startswith("debug") or found.startswith("debug"):
701 debugchoice[found] = (aliases, table[e])
701 debugchoice[found] = (aliases, table[e])
702 else:
702 else:
703 choice[found] = (aliases, table[e])
703 choice[found] = (aliases, table[e])
704
704
705 if not choice and debugchoice:
705 if not choice and debugchoice:
706 choice = debugchoice
706 choice = debugchoice
707
707
708 return choice, allcmds
708 return choice, allcmds
709
709
710 def findcmd(cmd, table, strict=True):
710 def findcmd(cmd, table, strict=True):
711 """Return (aliases, command table entry) for command string."""
711 """Return (aliases, command table entry) for command string."""
712 choice, allcmds = findpossible(cmd, table, strict)
712 choice, allcmds = findpossible(cmd, table, strict)
713
713
714 if cmd in choice:
714 if cmd in choice:
715 return choice[cmd]
715 return choice[cmd]
716
716
717 if len(choice) > 1:
717 if len(choice) > 1:
718 clist = sorted(choice)
718 clist = sorted(choice)
719 raise error.AmbiguousCommand(cmd, clist)
719 raise error.AmbiguousCommand(cmd, clist)
720
720
721 if choice:
721 if choice:
722 return list(choice.values())[0]
722 return list(choice.values())[0]
723
723
724 raise error.UnknownCommand(cmd, allcmds)
724 raise error.UnknownCommand(cmd, allcmds)
725
725
726 def changebranch(ui, repo, revs, label):
726 def changebranch(ui, repo, revs, label):
727 """ Change the branch name of given revs to label """
727 """ Change the branch name of given revs to label """
728
728
729 with repo.wlock(), repo.lock(), repo.transaction('branches'):
729 with repo.wlock(), repo.lock(), repo.transaction('branches'):
730 # abort in case of uncommitted merge or dirty wdir
730 # abort in case of uncommitted merge or dirty wdir
731 bailifchanged(repo)
731 bailifchanged(repo)
732 revs = scmutil.revrange(repo, revs)
732 revs = scmutil.revrange(repo, revs)
733 if not revs:
733 if not revs:
734 raise error.Abort("empty revision set")
734 raise error.Abort("empty revision set")
735 roots = repo.revs('roots(%ld)', revs)
735 roots = repo.revs('roots(%ld)', revs)
736 if len(roots) > 1:
736 if len(roots) > 1:
737 raise error.Abort(_("cannot change branch of non-linear revisions"))
737 raise error.Abort(_("cannot change branch of non-linear revisions"))
738 rewriteutil.precheck(repo, revs, 'change branch of')
738 rewriteutil.precheck(repo, revs, 'change branch of')
739
739
740 root = repo[roots.first()]
740 root = repo[roots.first()]
741 if not root.p1().branch() == label and label in repo.branchmap():
741 if not root.p1().branch() == label and label in repo.branchmap():
742 raise error.Abort(_("a branch of the same name already exists"))
742 raise error.Abort(_("a branch of the same name already exists"))
743
743
744 if repo.revs('merge() and %ld', revs):
744 if repo.revs('merge() and %ld', revs):
745 raise error.Abort(_("cannot change branch of a merge commit"))
745 raise error.Abort(_("cannot change branch of a merge commit"))
746 if repo.revs('obsolete() and %ld', revs):
746 if repo.revs('obsolete() and %ld', revs):
747 raise error.Abort(_("cannot change branch of a obsolete changeset"))
747 raise error.Abort(_("cannot change branch of a obsolete changeset"))
748
748
749 # make sure only topological heads
749 # make sure only topological heads
750 if repo.revs('heads(%ld) - head()', revs):
750 if repo.revs('heads(%ld) - head()', revs):
751 raise error.Abort(_("cannot change branch in middle of a stack"))
751 raise error.Abort(_("cannot change branch in middle of a stack"))
752
752
753 replacements = {}
753 replacements = {}
754 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
754 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
755 # mercurial.subrepo -> mercurial.cmdutil
755 # mercurial.subrepo -> mercurial.cmdutil
756 from . import context
756 from . import context
757 for rev in revs:
757 for rev in revs:
758 ctx = repo[rev]
758 ctx = repo[rev]
759 oldbranch = ctx.branch()
759 oldbranch = ctx.branch()
760 # check if ctx has same branch
760 # check if ctx has same branch
761 if oldbranch == label:
761 if oldbranch == label:
762 continue
762 continue
763
763
764 def filectxfn(repo, newctx, path):
764 def filectxfn(repo, newctx, path):
765 try:
765 try:
766 return ctx[path]
766 return ctx[path]
767 except error.ManifestLookupError:
767 except error.ManifestLookupError:
768 return None
768 return None
769
769
770 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
770 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
771 % (hex(ctx.node()), oldbranch, label))
771 % (hex(ctx.node()), oldbranch, label))
772 extra = ctx.extra()
772 extra = ctx.extra()
773 extra['branch_change'] = hex(ctx.node())
773 extra['branch_change'] = hex(ctx.node())
774 # While changing branch of set of linear commits, make sure that
774 # While changing branch of set of linear commits, make sure that
775 # we base our commits on new parent rather than old parent which
775 # we base our commits on new parent rather than old parent which
776 # was obsoleted while changing the branch
776 # was obsoleted while changing the branch
777 p1 = ctx.p1().node()
777 p1 = ctx.p1().node()
778 p2 = ctx.p2().node()
778 p2 = ctx.p2().node()
779 if p1 in replacements:
779 if p1 in replacements:
780 p1 = replacements[p1][0]
780 p1 = replacements[p1][0]
781 if p2 in replacements:
781 if p2 in replacements:
782 p2 = replacements[p2][0]
782 p2 = replacements[p2][0]
783
783
784 mc = context.memctx(repo, (p1, p2),
784 mc = context.memctx(repo, (p1, p2),
785 ctx.description(),
785 ctx.description(),
786 ctx.files(),
786 ctx.files(),
787 filectxfn,
787 filectxfn,
788 user=ctx.user(),
788 user=ctx.user(),
789 date=ctx.date(),
789 date=ctx.date(),
790 extra=extra,
790 extra=extra,
791 branch=label)
791 branch=label)
792
792
793 newnode = repo.commitctx(mc)
793 newnode = repo.commitctx(mc)
794 replacements[ctx.node()] = (newnode,)
794 replacements[ctx.node()] = (newnode,)
795 ui.debug('new node id is %s\n' % hex(newnode))
795 ui.debug('new node id is %s\n' % hex(newnode))
796
796
797 # create obsmarkers and move bookmarks
797 # create obsmarkers and move bookmarks
798 scmutil.cleanupnodes(repo, replacements, 'branch-change', fixphase=True)
798 scmutil.cleanupnodes(repo, replacements, 'branch-change', fixphase=True)
799
799
800 # move the working copy too
800 # move the working copy too
801 wctx = repo[None]
801 wctx = repo[None]
802 # in-progress merge is a bit too complex for now.
802 # in-progress merge is a bit too complex for now.
803 if len(wctx.parents()) == 1:
803 if len(wctx.parents()) == 1:
804 newid = replacements.get(wctx.p1().node())
804 newid = replacements.get(wctx.p1().node())
805 if newid is not None:
805 if newid is not None:
806 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
806 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
807 # mercurial.cmdutil
807 # mercurial.cmdutil
808 from . import hg
808 from . import hg
809 hg.update(repo, newid[0], quietempty=True)
809 hg.update(repo, newid[0], quietempty=True)
810
810
811 ui.status(_("changed branch on %d changesets\n") % len(replacements))
811 ui.status(_("changed branch on %d changesets\n") % len(replacements))
812
812
813 def findrepo(p):
813 def findrepo(p):
814 while not os.path.isdir(os.path.join(p, ".hg")):
814 while not os.path.isdir(os.path.join(p, ".hg")):
815 oldp, p = p, os.path.dirname(p)
815 oldp, p = p, os.path.dirname(p)
816 if p == oldp:
816 if p == oldp:
817 return None
817 return None
818
818
819 return p
819 return p
820
820
821 def bailifchanged(repo, merge=True, hint=None):
821 def bailifchanged(repo, merge=True, hint=None):
822 """ enforce the precondition that working directory must be clean.
822 """ enforce the precondition that working directory must be clean.
823
823
824 'merge' can be set to false if a pending uncommitted merge should be
824 'merge' can be set to false if a pending uncommitted merge should be
825 ignored (such as when 'update --check' runs).
825 ignored (such as when 'update --check' runs).
826
826
827 'hint' is the usual hint given to Abort exception.
827 'hint' is the usual hint given to Abort exception.
828 """
828 """
829
829
830 if merge and repo.dirstate.p2() != nullid:
830 if merge and repo.dirstate.p2() != nullid:
831 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
831 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
832 modified, added, removed, deleted = repo.status()[:4]
832 modified, added, removed, deleted = repo.status()[:4]
833 if modified or added or removed or deleted:
833 if modified or added or removed or deleted:
834 raise error.Abort(_('uncommitted changes'), hint=hint)
834 raise error.Abort(_('uncommitted changes'), hint=hint)
835 ctx = repo[None]
835 ctx = repo[None]
836 for s in sorted(ctx.substate):
836 for s in sorted(ctx.substate):
837 ctx.sub(s).bailifchanged(hint=hint)
837 ctx.sub(s).bailifchanged(hint=hint)
838
838
839 def logmessage(ui, opts):
839 def logmessage(ui, opts):
840 """ get the log message according to -m and -l option """
840 """ get the log message according to -m and -l option """
841 message = opts.get('message')
841 message = opts.get('message')
842 logfile = opts.get('logfile')
842 logfile = opts.get('logfile')
843
843
844 if message and logfile:
844 if message and logfile:
845 raise error.Abort(_('options --message and --logfile are mutually '
845 raise error.Abort(_('options --message and --logfile are mutually '
846 'exclusive'))
846 'exclusive'))
847 if not message and logfile:
847 if not message and logfile:
848 try:
848 try:
849 if isstdiofilename(logfile):
849 if isstdiofilename(logfile):
850 message = ui.fin.read()
850 message = ui.fin.read()
851 else:
851 else:
852 message = '\n'.join(util.readfile(logfile).splitlines())
852 message = '\n'.join(util.readfile(logfile).splitlines())
853 except IOError as inst:
853 except IOError as inst:
854 raise error.Abort(_("can't read commit message '%s': %s") %
854 raise error.Abort(_("can't read commit message '%s': %s") %
855 (logfile, encoding.strtolocal(inst.strerror)))
855 (logfile, encoding.strtolocal(inst.strerror)))
856 return message
856 return message
857
857
858 def mergeeditform(ctxorbool, baseformname):
858 def mergeeditform(ctxorbool, baseformname):
859 """return appropriate editform name (referencing a committemplate)
859 """return appropriate editform name (referencing a committemplate)
860
860
861 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
861 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
862 merging is committed.
862 merging is committed.
863
863
864 This returns baseformname with '.merge' appended if it is a merge,
864 This returns baseformname with '.merge' appended if it is a merge,
865 otherwise '.normal' is appended.
865 otherwise '.normal' is appended.
866 """
866 """
867 if isinstance(ctxorbool, bool):
867 if isinstance(ctxorbool, bool):
868 if ctxorbool:
868 if ctxorbool:
869 return baseformname + ".merge"
869 return baseformname + ".merge"
870 elif 1 < len(ctxorbool.parents()):
870 elif 1 < len(ctxorbool.parents()):
871 return baseformname + ".merge"
871 return baseformname + ".merge"
872
872
873 return baseformname + ".normal"
873 return baseformname + ".normal"
874
874
875 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
875 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
876 editform='', **opts):
876 editform='', **opts):
877 """get appropriate commit message editor according to '--edit' option
877 """get appropriate commit message editor according to '--edit' option
878
878
879 'finishdesc' is a function to be called with edited commit message
879 'finishdesc' is a function to be called with edited commit message
880 (= 'description' of the new changeset) just after editing, but
880 (= 'description' of the new changeset) just after editing, but
881 before checking empty-ness. It should return actual text to be
881 before checking empty-ness. It should return actual text to be
882 stored into history. This allows to change description before
882 stored into history. This allows to change description before
883 storing.
883 storing.
884
884
885 'extramsg' is a extra message to be shown in the editor instead of
885 'extramsg' is a extra message to be shown in the editor instead of
886 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
886 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
887 is automatically added.
887 is automatically added.
888
888
889 'editform' is a dot-separated list of names, to distinguish
889 'editform' is a dot-separated list of names, to distinguish
890 the purpose of commit text editing.
890 the purpose of commit text editing.
891
891
892 'getcommiteditor' returns 'commitforceeditor' regardless of
892 'getcommiteditor' returns 'commitforceeditor' regardless of
893 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
893 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
894 they are specific for usage in MQ.
894 they are specific for usage in MQ.
895 """
895 """
896 if edit or finishdesc or extramsg:
896 if edit or finishdesc or extramsg:
897 return lambda r, c, s: commitforceeditor(r, c, s,
897 return lambda r, c, s: commitforceeditor(r, c, s,
898 finishdesc=finishdesc,
898 finishdesc=finishdesc,
899 extramsg=extramsg,
899 extramsg=extramsg,
900 editform=editform)
900 editform=editform)
901 elif editform:
901 elif editform:
902 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
902 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
903 else:
903 else:
904 return commiteditor
904 return commiteditor
905
905
906 def _escapecommandtemplate(tmpl):
906 def _escapecommandtemplate(tmpl):
907 parts = []
907 parts = []
908 for typ, start, end in templater.scantemplate(tmpl, raw=True):
908 for typ, start, end in templater.scantemplate(tmpl, raw=True):
909 if typ == b'string':
909 if typ == b'string':
910 parts.append(stringutil.escapestr(tmpl[start:end]))
910 parts.append(stringutil.escapestr(tmpl[start:end]))
911 else:
911 else:
912 parts.append(tmpl[start:end])
912 parts.append(tmpl[start:end])
913 return b''.join(parts)
913 return b''.join(parts)
914
914
915 def rendercommandtemplate(ui, tmpl, props):
915 def rendercommandtemplate(ui, tmpl, props):
916 r"""Expand a literal template 'tmpl' in a way suitable for command line
916 r"""Expand a literal template 'tmpl' in a way suitable for command line
917
917
918 '\' in outermost string is not taken as an escape character because it
918 '\' in outermost string is not taken as an escape character because it
919 is a directory separator on Windows.
919 is a directory separator on Windows.
920
920
921 >>> from . import ui as uimod
921 >>> from . import ui as uimod
922 >>> ui = uimod.ui()
922 >>> ui = uimod.ui()
923 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
923 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
924 'c:\\foo'
924 'c:\\foo'
925 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
925 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
926 'c:{path}'
926 'c:{path}'
927 """
927 """
928 if not tmpl:
928 if not tmpl:
929 return tmpl
929 return tmpl
930 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
930 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
931 return t.renderdefault(props)
931 return t.renderdefault(props)
932
932
933 def rendertemplate(ctx, tmpl, props=None):
933 def rendertemplate(ctx, tmpl, props=None):
934 """Expand a literal template 'tmpl' byte-string against one changeset
934 """Expand a literal template 'tmpl' byte-string against one changeset
935
935
936 Each props item must be a stringify-able value or a callable returning
936 Each props item must be a stringify-able value or a callable returning
937 such value, i.e. no bare list nor dict should be passed.
937 such value, i.e. no bare list nor dict should be passed.
938 """
938 """
939 repo = ctx.repo()
939 repo = ctx.repo()
940 tres = formatter.templateresources(repo.ui, repo)
940 tres = formatter.templateresources(repo.ui, repo)
941 t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords,
941 t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords,
942 resources=tres)
942 resources=tres)
943 mapping = {'ctx': ctx}
943 mapping = {'ctx': ctx}
944 if props:
944 if props:
945 mapping.update(props)
945 mapping.update(props)
946 return t.renderdefault(mapping)
946 return t.renderdefault(mapping)
947
947
948 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
948 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
949 r"""Convert old-style filename format string to template string
949 r"""Convert old-style filename format string to template string
950
950
951 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
951 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
952 'foo-{reporoot|basename}-{seqno}.patch'
952 'foo-{reporoot|basename}-{seqno}.patch'
953 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
953 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
954 '{rev}{tags % "{tag}"}{node}'
954 '{rev}{tags % "{tag}"}{node}'
955
955
956 '\' in outermost strings has to be escaped because it is a directory
956 '\' in outermost strings has to be escaped because it is a directory
957 separator on Windows:
957 separator on Windows:
958
958
959 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
959 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
960 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
960 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
961 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
961 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
962 '\\\\\\\\foo\\\\bar.patch'
962 '\\\\\\\\foo\\\\bar.patch'
963 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
963 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
964 '\\\\{tags % "{tag}"}'
964 '\\\\{tags % "{tag}"}'
965
965
966 but inner strings follow the template rules (i.e. '\' is taken as an
966 but inner strings follow the template rules (i.e. '\' is taken as an
967 escape character):
967 escape character):
968
968
969 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
969 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
970 '{"c:\\tmp"}'
970 '{"c:\\tmp"}'
971 """
971 """
972 expander = {
972 expander = {
973 b'H': b'{node}',
973 b'H': b'{node}',
974 b'R': b'{rev}',
974 b'R': b'{rev}',
975 b'h': b'{node|short}',
975 b'h': b'{node|short}',
976 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
976 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
977 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
977 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
978 b'%': b'%',
978 b'%': b'%',
979 b'b': b'{reporoot|basename}',
979 b'b': b'{reporoot|basename}',
980 }
980 }
981 if total is not None:
981 if total is not None:
982 expander[b'N'] = b'{total}'
982 expander[b'N'] = b'{total}'
983 if seqno is not None:
983 if seqno is not None:
984 expander[b'n'] = b'{seqno}'
984 expander[b'n'] = b'{seqno}'
985 if total is not None and seqno is not None:
985 if total is not None and seqno is not None:
986 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
986 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
987 if pathname is not None:
987 if pathname is not None:
988 expander[b's'] = b'{pathname|basename}'
988 expander[b's'] = b'{pathname|basename}'
989 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
989 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
990 expander[b'p'] = b'{pathname}'
990 expander[b'p'] = b'{pathname}'
991
991
992 newname = []
992 newname = []
993 for typ, start, end in templater.scantemplate(pat, raw=True):
993 for typ, start, end in templater.scantemplate(pat, raw=True):
994 if typ != b'string':
994 if typ != b'string':
995 newname.append(pat[start:end])
995 newname.append(pat[start:end])
996 continue
996 continue
997 i = start
997 i = start
998 while i < end:
998 while i < end:
999 n = pat.find(b'%', i, end)
999 n = pat.find(b'%', i, end)
1000 if n < 0:
1000 if n < 0:
1001 newname.append(stringutil.escapestr(pat[i:end]))
1001 newname.append(stringutil.escapestr(pat[i:end]))
1002 break
1002 break
1003 newname.append(stringutil.escapestr(pat[i:n]))
1003 newname.append(stringutil.escapestr(pat[i:n]))
1004 if n + 2 > end:
1004 if n + 2 > end:
1005 raise error.Abort(_("incomplete format spec in output "
1005 raise error.Abort(_("incomplete format spec in output "
1006 "filename"))
1006 "filename"))
1007 c = pat[n + 1:n + 2]
1007 c = pat[n + 1:n + 2]
1008 i = n + 2
1008 i = n + 2
1009 try:
1009 try:
1010 newname.append(expander[c])
1010 newname.append(expander[c])
1011 except KeyError:
1011 except KeyError:
1012 raise error.Abort(_("invalid format spec '%%%s' in output "
1012 raise error.Abort(_("invalid format spec '%%%s' in output "
1013 "filename") % c)
1013 "filename") % c)
1014 return ''.join(newname)
1014 return ''.join(newname)
1015
1015
1016 def makefilename(ctx, pat, **props):
1016 def makefilename(ctx, pat, **props):
1017 if not pat:
1017 if not pat:
1018 return pat
1018 return pat
1019 tmpl = _buildfntemplate(pat, **props)
1019 tmpl = _buildfntemplate(pat, **props)
1020 # BUG: alias expansion shouldn't be made against template fragments
1020 # BUG: alias expansion shouldn't be made against template fragments
1021 # rewritten from %-format strings, but we have no easy way to partially
1021 # rewritten from %-format strings, but we have no easy way to partially
1022 # disable the expansion.
1022 # disable the expansion.
1023 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1023 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1024
1024
1025 def isstdiofilename(pat):
1025 def isstdiofilename(pat):
1026 """True if the given pat looks like a filename denoting stdin/stdout"""
1026 """True if the given pat looks like a filename denoting stdin/stdout"""
1027 return not pat or pat == '-'
1027 return not pat or pat == '-'
1028
1028
1029 class _unclosablefile(object):
1029 class _unclosablefile(object):
1030 def __init__(self, fp):
1030 def __init__(self, fp):
1031 self._fp = fp
1031 self._fp = fp
1032
1032
1033 def close(self):
1033 def close(self):
1034 pass
1034 pass
1035
1035
1036 def __iter__(self):
1036 def __iter__(self):
1037 return iter(self._fp)
1037 return iter(self._fp)
1038
1038
1039 def __getattr__(self, attr):
1039 def __getattr__(self, attr):
1040 return getattr(self._fp, attr)
1040 return getattr(self._fp, attr)
1041
1041
1042 def __enter__(self):
1042 def __enter__(self):
1043 return self
1043 return self
1044
1044
1045 def __exit__(self, exc_type, exc_value, exc_tb):
1045 def __exit__(self, exc_type, exc_value, exc_tb):
1046 pass
1046 pass
1047
1047
1048 def makefileobj(ctx, pat, mode='wb', **props):
1048 def makefileobj(ctx, pat, mode='wb', **props):
1049 writable = mode not in ('r', 'rb')
1049 writable = mode not in ('r', 'rb')
1050
1050
1051 if isstdiofilename(pat):
1051 if isstdiofilename(pat):
1052 repo = ctx.repo()
1052 repo = ctx.repo()
1053 if writable:
1053 if writable:
1054 fp = repo.ui.fout
1054 fp = repo.ui.fout
1055 else:
1055 else:
1056 fp = repo.ui.fin
1056 fp = repo.ui.fin
1057 return _unclosablefile(fp)
1057 return _unclosablefile(fp)
1058 fn = makefilename(ctx, pat, **props)
1058 fn = makefilename(ctx, pat, **props)
1059 return open(fn, mode)
1059 return open(fn, mode)
1060
1060
1061 def openrevlog(repo, cmd, file_, opts):
1061 def openrevlog(repo, cmd, file_, opts):
1062 """opens the changelog, manifest, a filelog or a given revlog"""
1062 """opens the changelog, manifest, a filelog or a given revlog"""
1063 cl = opts['changelog']
1063 cl = opts['changelog']
1064 mf = opts['manifest']
1064 mf = opts['manifest']
1065 dir = opts['dir']
1065 dir = opts['dir']
1066 msg = None
1066 msg = None
1067 if cl and mf:
1067 if cl and mf:
1068 msg = _('cannot specify --changelog and --manifest at the same time')
1068 msg = _('cannot specify --changelog and --manifest at the same time')
1069 elif cl and dir:
1069 elif cl and dir:
1070 msg = _('cannot specify --changelog and --dir at the same time')
1070 msg = _('cannot specify --changelog and --dir at the same time')
1071 elif cl or mf or dir:
1071 elif cl or mf or dir:
1072 if file_:
1072 if file_:
1073 msg = _('cannot specify filename with --changelog or --manifest')
1073 msg = _('cannot specify filename with --changelog or --manifest')
1074 elif not repo:
1074 elif not repo:
1075 msg = _('cannot specify --changelog or --manifest or --dir '
1075 msg = _('cannot specify --changelog or --manifest or --dir '
1076 'without a repository')
1076 'without a repository')
1077 if msg:
1077 if msg:
1078 raise error.Abort(msg)
1078 raise error.Abort(msg)
1079
1079
1080 r = None
1080 r = None
1081 if repo:
1081 if repo:
1082 if cl:
1082 if cl:
1083 r = repo.unfiltered().changelog
1083 r = repo.unfiltered().changelog
1084 elif dir:
1084 elif dir:
1085 if 'treemanifest' not in repo.requirements:
1085 if 'treemanifest' not in repo.requirements:
1086 raise error.Abort(_("--dir can only be used on repos with "
1086 raise error.Abort(_("--dir can only be used on repos with "
1087 "treemanifest enabled"))
1087 "treemanifest enabled"))
1088 if not dir.endswith('/'):
1088 if not dir.endswith('/'):
1089 dir = dir + '/'
1089 dir = dir + '/'
1090 dirlog = repo.manifestlog._revlog.dirlog(dir)
1090 dirlog = repo.manifestlog._revlog.dirlog(dir)
1091 if len(dirlog):
1091 if len(dirlog):
1092 r = dirlog
1092 r = dirlog
1093 elif mf:
1093 elif mf:
1094 r = repo.manifestlog._revlog
1094 r = repo.manifestlog._revlog
1095 elif file_:
1095 elif file_:
1096 filelog = repo.file(file_)
1096 filelog = repo.file(file_)
1097 if len(filelog):
1097 if len(filelog):
1098 r = filelog
1098 r = filelog
1099 if not r:
1099 if not r:
1100 if not file_:
1100 if not file_:
1101 raise error.CommandError(cmd, _('invalid arguments'))
1101 raise error.CommandError(cmd, _('invalid arguments'))
1102 if not os.path.isfile(file_):
1102 if not os.path.isfile(file_):
1103 raise error.Abort(_("revlog '%s' not found") % file_)
1103 raise error.Abort(_("revlog '%s' not found") % file_)
1104 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
1104 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
1105 file_[:-2] + ".i")
1105 file_[:-2] + ".i")
1106 return r
1106 return r
1107
1107
1108 def copy(ui, repo, pats, opts, rename=False):
1108 def copy(ui, repo, pats, opts, rename=False):
1109 # called with the repo lock held
1109 # called with the repo lock held
1110 #
1110 #
1111 # hgsep => pathname that uses "/" to separate directories
1111 # hgsep => pathname that uses "/" to separate directories
1112 # ossep => pathname that uses os.sep to separate directories
1112 # ossep => pathname that uses os.sep to separate directories
1113 cwd = repo.getcwd()
1113 cwd = repo.getcwd()
1114 targets = {}
1114 targets = {}
1115 after = opts.get("after")
1115 after = opts.get("after")
1116 dryrun = opts.get("dry_run")
1116 dryrun = opts.get("dry_run")
1117 wctx = repo[None]
1117 wctx = repo[None]
1118
1118
1119 def walkpat(pat):
1119 def walkpat(pat):
1120 srcs = []
1120 srcs = []
1121 if after:
1121 if after:
1122 badstates = '?'
1122 badstates = '?'
1123 else:
1123 else:
1124 badstates = '?r'
1124 badstates = '?r'
1125 m = scmutil.match(wctx, [pat], opts, globbed=True)
1125 m = scmutil.match(wctx, [pat], opts, globbed=True)
1126 for abs in wctx.walk(m):
1126 for abs in wctx.walk(m):
1127 state = repo.dirstate[abs]
1127 state = repo.dirstate[abs]
1128 rel = m.rel(abs)
1128 rel = m.rel(abs)
1129 exact = m.exact(abs)
1129 exact = m.exact(abs)
1130 if state in badstates:
1130 if state in badstates:
1131 if exact and state == '?':
1131 if exact and state == '?':
1132 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1132 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1133 if exact and state == 'r':
1133 if exact and state == 'r':
1134 ui.warn(_('%s: not copying - file has been marked for'
1134 ui.warn(_('%s: not copying - file has been marked for'
1135 ' remove\n') % rel)
1135 ' remove\n') % rel)
1136 continue
1136 continue
1137 # abs: hgsep
1137 # abs: hgsep
1138 # rel: ossep
1138 # rel: ossep
1139 srcs.append((abs, rel, exact))
1139 srcs.append((abs, rel, exact))
1140 return srcs
1140 return srcs
1141
1141
1142 # abssrc: hgsep
1142 # abssrc: hgsep
1143 # relsrc: ossep
1143 # relsrc: ossep
1144 # otarget: ossep
1144 # otarget: ossep
1145 def copyfile(abssrc, relsrc, otarget, exact):
1145 def copyfile(abssrc, relsrc, otarget, exact):
1146 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1146 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1147 if '/' in abstarget:
1147 if '/' in abstarget:
1148 # We cannot normalize abstarget itself, this would prevent
1148 # We cannot normalize abstarget itself, this would prevent
1149 # case only renames, like a => A.
1149 # case only renames, like a => A.
1150 abspath, absname = abstarget.rsplit('/', 1)
1150 abspath, absname = abstarget.rsplit('/', 1)
1151 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1151 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1152 reltarget = repo.pathto(abstarget, cwd)
1152 reltarget = repo.pathto(abstarget, cwd)
1153 target = repo.wjoin(abstarget)
1153 target = repo.wjoin(abstarget)
1154 src = repo.wjoin(abssrc)
1154 src = repo.wjoin(abssrc)
1155 state = repo.dirstate[abstarget]
1155 state = repo.dirstate[abstarget]
1156
1156
1157 scmutil.checkportable(ui, abstarget)
1157 scmutil.checkportable(ui, abstarget)
1158
1158
1159 # check for collisions
1159 # check for collisions
1160 prevsrc = targets.get(abstarget)
1160 prevsrc = targets.get(abstarget)
1161 if prevsrc is not None:
1161 if prevsrc is not None:
1162 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1162 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1163 (reltarget, repo.pathto(abssrc, cwd),
1163 (reltarget, repo.pathto(abssrc, cwd),
1164 repo.pathto(prevsrc, cwd)))
1164 repo.pathto(prevsrc, cwd)))
1165 return
1165 return
1166
1166
1167 # check for overwrites
1167 # check for overwrites
1168 exists = os.path.lexists(target)
1168 exists = os.path.lexists(target)
1169 samefile = False
1169 samefile = False
1170 if exists and abssrc != abstarget:
1170 if exists and abssrc != abstarget:
1171 if (repo.dirstate.normalize(abssrc) ==
1171 if (repo.dirstate.normalize(abssrc) ==
1172 repo.dirstate.normalize(abstarget)):
1172 repo.dirstate.normalize(abstarget)):
1173 if not rename:
1173 if not rename:
1174 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1174 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1175 return
1175 return
1176 exists = False
1176 exists = False
1177 samefile = True
1177 samefile = True
1178
1178
1179 if not after and exists or after and state in 'mn':
1179 if not after and exists or after and state in 'mn':
1180 if not opts['force']:
1180 if not opts['force']:
1181 if state in 'mn':
1181 if state in 'mn':
1182 msg = _('%s: not overwriting - file already committed\n')
1182 msg = _('%s: not overwriting - file already committed\n')
1183 if after:
1183 if after:
1184 flags = '--after --force'
1184 flags = '--after --force'
1185 else:
1185 else:
1186 flags = '--force'
1186 flags = '--force'
1187 if rename:
1187 if rename:
1188 hint = _('(hg rename %s to replace the file by '
1188 hint = _('(hg rename %s to replace the file by '
1189 'recording a rename)\n') % flags
1189 'recording a rename)\n') % flags
1190 else:
1190 else:
1191 hint = _('(hg copy %s to replace the file by '
1191 hint = _('(hg copy %s to replace the file by '
1192 'recording a copy)\n') % flags
1192 'recording a copy)\n') % flags
1193 else:
1193 else:
1194 msg = _('%s: not overwriting - file exists\n')
1194 msg = _('%s: not overwriting - file exists\n')
1195 if rename:
1195 if rename:
1196 hint = _('(hg rename --after to record the rename)\n')
1196 hint = _('(hg rename --after to record the rename)\n')
1197 else:
1197 else:
1198 hint = _('(hg copy --after to record the copy)\n')
1198 hint = _('(hg copy --after to record the copy)\n')
1199 ui.warn(msg % reltarget)
1199 ui.warn(msg % reltarget)
1200 ui.warn(hint)
1200 ui.warn(hint)
1201 return
1201 return
1202
1202
1203 if after:
1203 if after:
1204 if not exists:
1204 if not exists:
1205 if rename:
1205 if rename:
1206 ui.warn(_('%s: not recording move - %s does not exist\n') %
1206 ui.warn(_('%s: not recording move - %s does not exist\n') %
1207 (relsrc, reltarget))
1207 (relsrc, reltarget))
1208 else:
1208 else:
1209 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1209 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1210 (relsrc, reltarget))
1210 (relsrc, reltarget))
1211 return
1211 return
1212 elif not dryrun:
1212 elif not dryrun:
1213 try:
1213 try:
1214 if exists:
1214 if exists:
1215 os.unlink(target)
1215 os.unlink(target)
1216 targetdir = os.path.dirname(target) or '.'
1216 targetdir = os.path.dirname(target) or '.'
1217 if not os.path.isdir(targetdir):
1217 if not os.path.isdir(targetdir):
1218 os.makedirs(targetdir)
1218 os.makedirs(targetdir)
1219 if samefile:
1219 if samefile:
1220 tmp = target + "~hgrename"
1220 tmp = target + "~hgrename"
1221 os.rename(src, tmp)
1221 os.rename(src, tmp)
1222 os.rename(tmp, target)
1222 os.rename(tmp, target)
1223 else:
1223 else:
1224 # Preserve stat info on renames, not on copies; this matches
1224 # Preserve stat info on renames, not on copies; this matches
1225 # Linux CLI behavior.
1225 # Linux CLI behavior.
1226 util.copyfile(src, target, copystat=rename)
1226 util.copyfile(src, target, copystat=rename)
1227 srcexists = True
1227 srcexists = True
1228 except IOError as inst:
1228 except IOError as inst:
1229 if inst.errno == errno.ENOENT:
1229 if inst.errno == errno.ENOENT:
1230 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1230 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1231 srcexists = False
1231 srcexists = False
1232 else:
1232 else:
1233 ui.warn(_('%s: cannot copy - %s\n') %
1233 ui.warn(_('%s: cannot copy - %s\n') %
1234 (relsrc, encoding.strtolocal(inst.strerror)))
1234 (relsrc, encoding.strtolocal(inst.strerror)))
1235 return True # report a failure
1235 return True # report a failure
1236
1236
1237 if ui.verbose or not exact:
1237 if ui.verbose or not exact:
1238 if rename:
1238 if rename:
1239 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1239 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1240 else:
1240 else:
1241 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1241 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1242
1242
1243 targets[abstarget] = abssrc
1243 targets[abstarget] = abssrc
1244
1244
1245 # fix up dirstate
1245 # fix up dirstate
1246 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1246 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1247 dryrun=dryrun, cwd=cwd)
1247 dryrun=dryrun, cwd=cwd)
1248 if rename and not dryrun:
1248 if rename and not dryrun:
1249 if not after and srcexists and not samefile:
1249 if not after and srcexists and not samefile:
1250 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
1250 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
1251 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1251 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1252 wctx.forget([abssrc])
1252 wctx.forget([abssrc])
1253
1253
1254 # pat: ossep
1254 # pat: ossep
1255 # dest ossep
1255 # dest ossep
1256 # srcs: list of (hgsep, hgsep, ossep, bool)
1256 # srcs: list of (hgsep, hgsep, ossep, bool)
1257 # return: function that takes hgsep and returns ossep
1257 # return: function that takes hgsep and returns ossep
1258 def targetpathfn(pat, dest, srcs):
1258 def targetpathfn(pat, dest, srcs):
1259 if os.path.isdir(pat):
1259 if os.path.isdir(pat):
1260 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1260 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1261 abspfx = util.localpath(abspfx)
1261 abspfx = util.localpath(abspfx)
1262 if destdirexists:
1262 if destdirexists:
1263 striplen = len(os.path.split(abspfx)[0])
1263 striplen = len(os.path.split(abspfx)[0])
1264 else:
1264 else:
1265 striplen = len(abspfx)
1265 striplen = len(abspfx)
1266 if striplen:
1266 if striplen:
1267 striplen += len(pycompat.ossep)
1267 striplen += len(pycompat.ossep)
1268 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1268 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1269 elif destdirexists:
1269 elif destdirexists:
1270 res = lambda p: os.path.join(dest,
1270 res = lambda p: os.path.join(dest,
1271 os.path.basename(util.localpath(p)))
1271 os.path.basename(util.localpath(p)))
1272 else:
1272 else:
1273 res = lambda p: dest
1273 res = lambda p: dest
1274 return res
1274 return res
1275
1275
1276 # pat: ossep
1276 # pat: ossep
1277 # dest ossep
1277 # dest ossep
1278 # srcs: list of (hgsep, hgsep, ossep, bool)
1278 # srcs: list of (hgsep, hgsep, ossep, bool)
1279 # return: function that takes hgsep and returns ossep
1279 # return: function that takes hgsep and returns ossep
1280 def targetpathafterfn(pat, dest, srcs):
1280 def targetpathafterfn(pat, dest, srcs):
1281 if matchmod.patkind(pat):
1281 if matchmod.patkind(pat):
1282 # a mercurial pattern
1282 # a mercurial pattern
1283 res = lambda p: os.path.join(dest,
1283 res = lambda p: os.path.join(dest,
1284 os.path.basename(util.localpath(p)))
1284 os.path.basename(util.localpath(p)))
1285 else:
1285 else:
1286 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1286 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1287 if len(abspfx) < len(srcs[0][0]):
1287 if len(abspfx) < len(srcs[0][0]):
1288 # A directory. Either the target path contains the last
1288 # A directory. Either the target path contains the last
1289 # component of the source path or it does not.
1289 # component of the source path or it does not.
1290 def evalpath(striplen):
1290 def evalpath(striplen):
1291 score = 0
1291 score = 0
1292 for s in srcs:
1292 for s in srcs:
1293 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1293 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1294 if os.path.lexists(t):
1294 if os.path.lexists(t):
1295 score += 1
1295 score += 1
1296 return score
1296 return score
1297
1297
1298 abspfx = util.localpath(abspfx)
1298 abspfx = util.localpath(abspfx)
1299 striplen = len(abspfx)
1299 striplen = len(abspfx)
1300 if striplen:
1300 if striplen:
1301 striplen += len(pycompat.ossep)
1301 striplen += len(pycompat.ossep)
1302 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1302 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1303 score = evalpath(striplen)
1303 score = evalpath(striplen)
1304 striplen1 = len(os.path.split(abspfx)[0])
1304 striplen1 = len(os.path.split(abspfx)[0])
1305 if striplen1:
1305 if striplen1:
1306 striplen1 += len(pycompat.ossep)
1306 striplen1 += len(pycompat.ossep)
1307 if evalpath(striplen1) > score:
1307 if evalpath(striplen1) > score:
1308 striplen = striplen1
1308 striplen = striplen1
1309 res = lambda p: os.path.join(dest,
1309 res = lambda p: os.path.join(dest,
1310 util.localpath(p)[striplen:])
1310 util.localpath(p)[striplen:])
1311 else:
1311 else:
1312 # a file
1312 # a file
1313 if destdirexists:
1313 if destdirexists:
1314 res = lambda p: os.path.join(dest,
1314 res = lambda p: os.path.join(dest,
1315 os.path.basename(util.localpath(p)))
1315 os.path.basename(util.localpath(p)))
1316 else:
1316 else:
1317 res = lambda p: dest
1317 res = lambda p: dest
1318 return res
1318 return res
1319
1319
1320 pats = scmutil.expandpats(pats)
1320 pats = scmutil.expandpats(pats)
1321 if not pats:
1321 if not pats:
1322 raise error.Abort(_('no source or destination specified'))
1322 raise error.Abort(_('no source or destination specified'))
1323 if len(pats) == 1:
1323 if len(pats) == 1:
1324 raise error.Abort(_('no destination specified'))
1324 raise error.Abort(_('no destination specified'))
1325 dest = pats.pop()
1325 dest = pats.pop()
1326 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1326 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1327 if not destdirexists:
1327 if not destdirexists:
1328 if len(pats) > 1 or matchmod.patkind(pats[0]):
1328 if len(pats) > 1 or matchmod.patkind(pats[0]):
1329 raise error.Abort(_('with multiple sources, destination must be an '
1329 raise error.Abort(_('with multiple sources, destination must be an '
1330 'existing directory'))
1330 'existing directory'))
1331 if util.endswithsep(dest):
1331 if util.endswithsep(dest):
1332 raise error.Abort(_('destination %s is not a directory') % dest)
1332 raise error.Abort(_('destination %s is not a directory') % dest)
1333
1333
1334 tfn = targetpathfn
1334 tfn = targetpathfn
1335 if after:
1335 if after:
1336 tfn = targetpathafterfn
1336 tfn = targetpathafterfn
1337 copylist = []
1337 copylist = []
1338 for pat in pats:
1338 for pat in pats:
1339 srcs = walkpat(pat)
1339 srcs = walkpat(pat)
1340 if not srcs:
1340 if not srcs:
1341 continue
1341 continue
1342 copylist.append((tfn(pat, dest, srcs), srcs))
1342 copylist.append((tfn(pat, dest, srcs), srcs))
1343 if not copylist:
1343 if not copylist:
1344 raise error.Abort(_('no files to copy'))
1344 raise error.Abort(_('no files to copy'))
1345
1345
1346 errors = 0
1346 errors = 0
1347 for targetpath, srcs in copylist:
1347 for targetpath, srcs in copylist:
1348 for abssrc, relsrc, exact in srcs:
1348 for abssrc, relsrc, exact in srcs:
1349 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1349 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1350 errors += 1
1350 errors += 1
1351
1351
1352 if errors:
1352 if errors:
1353 ui.warn(_('(consider using --after)\n'))
1353 ui.warn(_('(consider using --after)\n'))
1354
1354
1355 return errors != 0
1355 return errors != 0
1356
1356
1357 ## facility to let extension process additional data into an import patch
1357 ## facility to let extension process additional data into an import patch
1358 # list of identifier to be executed in order
1358 # list of identifier to be executed in order
1359 extrapreimport = [] # run before commit
1359 extrapreimport = [] # run before commit
1360 extrapostimport = [] # run after commit
1360 extrapostimport = [] # run after commit
1361 # mapping from identifier to actual import function
1361 # mapping from identifier to actual import function
1362 #
1362 #
1363 # 'preimport' are run before the commit is made and are provided the following
1363 # 'preimport' are run before the commit is made and are provided the following
1364 # arguments:
1364 # arguments:
1365 # - repo: the localrepository instance,
1365 # - repo: the localrepository instance,
1366 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1366 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1367 # - extra: the future extra dictionary of the changeset, please mutate it,
1367 # - extra: the future extra dictionary of the changeset, please mutate it,
1368 # - opts: the import options.
1368 # - opts: the import options.
1369 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1369 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1370 # mutation of in memory commit and more. Feel free to rework the code to get
1370 # mutation of in memory commit and more. Feel free to rework the code to get
1371 # there.
1371 # there.
1372 extrapreimportmap = {}
1372 extrapreimportmap = {}
1373 # 'postimport' are run after the commit is made and are provided the following
1373 # 'postimport' are run after the commit is made and are provided the following
1374 # argument:
1374 # argument:
1375 # - ctx: the changectx created by import.
1375 # - ctx: the changectx created by import.
1376 extrapostimportmap = {}
1376 extrapostimportmap = {}
1377
1377
1378 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1378 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1379 """Utility function used by commands.import to import a single patch
1379 """Utility function used by commands.import to import a single patch
1380
1380
1381 This function is explicitly defined here to help the evolve extension to
1381 This function is explicitly defined here to help the evolve extension to
1382 wrap this part of the import logic.
1382 wrap this part of the import logic.
1383
1383
1384 The API is currently a bit ugly because it a simple code translation from
1384 The API is currently a bit ugly because it a simple code translation from
1385 the import command. Feel free to make it better.
1385 the import command. Feel free to make it better.
1386
1386
1387 :patchdata: a dictionary containing parsed patch data (such as from
1387 :patchdata: a dictionary containing parsed patch data (such as from
1388 ``patch.extract()``)
1388 ``patch.extract()``)
1389 :parents: nodes that will be parent of the created commit
1389 :parents: nodes that will be parent of the created commit
1390 :opts: the full dict of option passed to the import command
1390 :opts: the full dict of option passed to the import command
1391 :msgs: list to save commit message to.
1391 :msgs: list to save commit message to.
1392 (used in case we need to save it when failing)
1392 (used in case we need to save it when failing)
1393 :updatefunc: a function that update a repo to a given node
1393 :updatefunc: a function that update a repo to a given node
1394 updatefunc(<repo>, <node>)
1394 updatefunc(<repo>, <node>)
1395 """
1395 """
1396 # avoid cycle context -> subrepo -> cmdutil
1396 # avoid cycle context -> subrepo -> cmdutil
1397 from . import context
1397 from . import context
1398
1398
1399 tmpname = patchdata.get('filename')
1399 tmpname = patchdata.get('filename')
1400 message = patchdata.get('message')
1400 message = patchdata.get('message')
1401 user = opts.get('user') or patchdata.get('user')
1401 user = opts.get('user') or patchdata.get('user')
1402 date = opts.get('date') or patchdata.get('date')
1402 date = opts.get('date') or patchdata.get('date')
1403 branch = patchdata.get('branch')
1403 branch = patchdata.get('branch')
1404 nodeid = patchdata.get('nodeid')
1404 nodeid = patchdata.get('nodeid')
1405 p1 = patchdata.get('p1')
1405 p1 = patchdata.get('p1')
1406 p2 = patchdata.get('p2')
1406 p2 = patchdata.get('p2')
1407
1407
1408 nocommit = opts.get('no_commit')
1408 nocommit = opts.get('no_commit')
1409 importbranch = opts.get('import_branch')
1409 importbranch = opts.get('import_branch')
1410 update = not opts.get('bypass')
1410 update = not opts.get('bypass')
1411 strip = opts["strip"]
1411 strip = opts["strip"]
1412 prefix = opts["prefix"]
1412 prefix = opts["prefix"]
1413 sim = float(opts.get('similarity') or 0)
1413 sim = float(opts.get('similarity') or 0)
1414
1414
1415 if not tmpname:
1415 if not tmpname:
1416 return None, None, False
1416 return None, None, False
1417
1417
1418 rejects = False
1418 rejects = False
1419
1419
1420 cmdline_message = logmessage(ui, opts)
1420 cmdline_message = logmessage(ui, opts)
1421 if cmdline_message:
1421 if cmdline_message:
1422 # pickup the cmdline msg
1422 # pickup the cmdline msg
1423 message = cmdline_message
1423 message = cmdline_message
1424 elif message:
1424 elif message:
1425 # pickup the patch msg
1425 # pickup the patch msg
1426 message = message.strip()
1426 message = message.strip()
1427 else:
1427 else:
1428 # launch the editor
1428 # launch the editor
1429 message = None
1429 message = None
1430 ui.debug('message:\n%s\n' % (message or ''))
1430 ui.debug('message:\n%s\n' % (message or ''))
1431
1431
1432 if len(parents) == 1:
1432 if len(parents) == 1:
1433 parents.append(repo[nullid])
1433 parents.append(repo[nullid])
1434 if opts.get('exact'):
1434 if opts.get('exact'):
1435 if not nodeid or not p1:
1435 if not nodeid or not p1:
1436 raise error.Abort(_('not a Mercurial patch'))
1436 raise error.Abort(_('not a Mercurial patch'))
1437 p1 = repo[p1]
1437 p1 = repo[p1]
1438 p2 = repo[p2 or nullid]
1438 p2 = repo[p2 or nullid]
1439 elif p2:
1439 elif p2:
1440 try:
1440 try:
1441 p1 = repo[p1]
1441 p1 = repo[p1]
1442 p2 = repo[p2]
1442 p2 = repo[p2]
1443 # Without any options, consider p2 only if the
1443 # Without any options, consider p2 only if the
1444 # patch is being applied on top of the recorded
1444 # patch is being applied on top of the recorded
1445 # first parent.
1445 # first parent.
1446 if p1 != parents[0]:
1446 if p1 != parents[0]:
1447 p1 = parents[0]
1447 p1 = parents[0]
1448 p2 = repo[nullid]
1448 p2 = repo[nullid]
1449 except error.RepoError:
1449 except error.RepoError:
1450 p1, p2 = parents
1450 p1, p2 = parents
1451 if p2.node() == nullid:
1451 if p2.node() == nullid:
1452 ui.warn(_("warning: import the patch as a normal revision\n"
1452 ui.warn(_("warning: import the patch as a normal revision\n"
1453 "(use --exact to import the patch as a merge)\n"))
1453 "(use --exact to import the patch as a merge)\n"))
1454 else:
1454 else:
1455 p1, p2 = parents
1455 p1, p2 = parents
1456
1456
1457 n = None
1457 n = None
1458 if update:
1458 if update:
1459 if p1 != parents[0]:
1459 if p1 != parents[0]:
1460 updatefunc(repo, p1.node())
1460 updatefunc(repo, p1.node())
1461 if p2 != parents[1]:
1461 if p2 != parents[1]:
1462 repo.setparents(p1.node(), p2.node())
1462 repo.setparents(p1.node(), p2.node())
1463
1463
1464 if opts.get('exact') or importbranch:
1464 if opts.get('exact') or importbranch:
1465 repo.dirstate.setbranch(branch or 'default')
1465 repo.dirstate.setbranch(branch or 'default')
1466
1466
1467 partial = opts.get('partial', False)
1467 partial = opts.get('partial', False)
1468 files = set()
1468 files = set()
1469 try:
1469 try:
1470 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1470 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1471 files=files, eolmode=None, similarity=sim / 100.0)
1471 files=files, eolmode=None, similarity=sim / 100.0)
1472 except error.PatchError as e:
1472 except error.PatchError as e:
1473 if not partial:
1473 if not partial:
1474 raise error.Abort(pycompat.bytestr(e))
1474 raise error.Abort(pycompat.bytestr(e))
1475 if partial:
1475 if partial:
1476 rejects = True
1476 rejects = True
1477
1477
1478 files = list(files)
1478 files = list(files)
1479 if nocommit:
1479 if nocommit:
1480 if message:
1480 if message:
1481 msgs.append(message)
1481 msgs.append(message)
1482 else:
1482 else:
1483 if opts.get('exact') or p2:
1483 if opts.get('exact') or p2:
1484 # If you got here, you either use --force and know what
1484 # If you got here, you either use --force and know what
1485 # you are doing or used --exact or a merge patch while
1485 # you are doing or used --exact or a merge patch while
1486 # being updated to its first parent.
1486 # being updated to its first parent.
1487 m = None
1487 m = None
1488 else:
1488 else:
1489 m = scmutil.matchfiles(repo, files or [])
1489 m = scmutil.matchfiles(repo, files or [])
1490 editform = mergeeditform(repo[None], 'import.normal')
1490 editform = mergeeditform(repo[None], 'import.normal')
1491 if opts.get('exact'):
1491 if opts.get('exact'):
1492 editor = None
1492 editor = None
1493 else:
1493 else:
1494 editor = getcommiteditor(editform=editform,
1494 editor = getcommiteditor(editform=editform,
1495 **pycompat.strkwargs(opts))
1495 **pycompat.strkwargs(opts))
1496 extra = {}
1496 extra = {}
1497 for idfunc in extrapreimport:
1497 for idfunc in extrapreimport:
1498 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1498 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1499 overrides = {}
1499 overrides = {}
1500 if partial:
1500 if partial:
1501 overrides[('ui', 'allowemptycommit')] = True
1501 overrides[('ui', 'allowemptycommit')] = True
1502 with repo.ui.configoverride(overrides, 'import'):
1502 with repo.ui.configoverride(overrides, 'import'):
1503 n = repo.commit(message, user,
1503 n = repo.commit(message, user,
1504 date, match=m,
1504 date, match=m,
1505 editor=editor, extra=extra)
1505 editor=editor, extra=extra)
1506 for idfunc in extrapostimport:
1506 for idfunc in extrapostimport:
1507 extrapostimportmap[idfunc](repo[n])
1507 extrapostimportmap[idfunc](repo[n])
1508 else:
1508 else:
1509 if opts.get('exact') or importbranch:
1509 if opts.get('exact') or importbranch:
1510 branch = branch or 'default'
1510 branch = branch or 'default'
1511 else:
1511 else:
1512 branch = p1.branch()
1512 branch = p1.branch()
1513 store = patch.filestore()
1513 store = patch.filestore()
1514 try:
1514 try:
1515 files = set()
1515 files = set()
1516 try:
1516 try:
1517 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1517 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1518 files, eolmode=None)
1518 files, eolmode=None)
1519 except error.PatchError as e:
1519 except error.PatchError as e:
1520 raise error.Abort(stringutil.forcebytestr(e))
1520 raise error.Abort(stringutil.forcebytestr(e))
1521 if opts.get('exact'):
1521 if opts.get('exact'):
1522 editor = None
1522 editor = None
1523 else:
1523 else:
1524 editor = getcommiteditor(editform='import.bypass')
1524 editor = getcommiteditor(editform='import.bypass')
1525 memctx = context.memctx(repo, (p1.node(), p2.node()),
1525 memctx = context.memctx(repo, (p1.node(), p2.node()),
1526 message,
1526 message,
1527 files=files,
1527 files=files,
1528 filectxfn=store,
1528 filectxfn=store,
1529 user=user,
1529 user=user,
1530 date=date,
1530 date=date,
1531 branch=branch,
1531 branch=branch,
1532 editor=editor)
1532 editor=editor)
1533 n = memctx.commit()
1533 n = memctx.commit()
1534 finally:
1534 finally:
1535 store.close()
1535 store.close()
1536 if opts.get('exact') and nocommit:
1536 if opts.get('exact') and nocommit:
1537 # --exact with --no-commit is still useful in that it does merge
1537 # --exact with --no-commit is still useful in that it does merge
1538 # and branch bits
1538 # and branch bits
1539 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1539 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1540 elif opts.get('exact') and (not n or hex(n) != nodeid):
1540 elif opts.get('exact') and (not n or hex(n) != nodeid):
1541 raise error.Abort(_('patch is damaged or loses information'))
1541 raise error.Abort(_('patch is damaged or loses information'))
1542 msg = _('applied to working directory')
1542 msg = _('applied to working directory')
1543 if n:
1543 if n:
1544 # i18n: refers to a short changeset id
1544 # i18n: refers to a short changeset id
1545 msg = _('created %s') % short(n)
1545 msg = _('created %s') % short(n)
1546 return msg, n, rejects
1546 return msg, n, rejects
1547
1547
1548 # facility to let extensions include additional data in an exported patch
1548 # facility to let extensions include additional data in an exported patch
1549 # list of identifiers to be executed in order
1549 # list of identifiers to be executed in order
1550 extraexport = []
1550 extraexport = []
1551 # mapping from identifier to actual export function
1551 # mapping from identifier to actual export function
1552 # function as to return a string to be added to the header or None
1552 # function as to return a string to be added to the header or None
1553 # it is given two arguments (sequencenumber, changectx)
1553 # it is given two arguments (sequencenumber, changectx)
1554 extraexportmap = {}
1554 extraexportmap = {}
1555
1555
1556 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1556 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1557 node = scmutil.binnode(ctx)
1557 node = scmutil.binnode(ctx)
1558 parents = [p.node() for p in ctx.parents() if p]
1558 parents = [p.node() for p in ctx.parents() if p]
1559 branch = ctx.branch()
1559 branch = ctx.branch()
1560 if switch_parent:
1560 if switch_parent:
1561 parents.reverse()
1561 parents.reverse()
1562
1562
1563 if parents:
1563 if parents:
1564 prev = parents[0]
1564 prev = parents[0]
1565 else:
1565 else:
1566 prev = nullid
1566 prev = nullid
1567
1567
1568 fm.context(ctx=ctx)
1568 fm.context(ctx=ctx)
1569 fm.plain('# HG changeset patch\n')
1569 fm.plain('# HG changeset patch\n')
1570 fm.write('user', '# User %s\n', ctx.user())
1570 fm.write('user', '# User %s\n', ctx.user())
1571 fm.plain('# Date %d %d\n' % ctx.date())
1571 fm.plain('# Date %d %d\n' % ctx.date())
1572 fm.write('date', '# %s\n', fm.formatdate(ctx.date()))
1572 fm.write('date', '# %s\n', fm.formatdate(ctx.date()))
1573 fm.condwrite(branch and branch != 'default',
1573 fm.condwrite(branch and branch != 'default',
1574 'branch', '# Branch %s\n', branch)
1574 'branch', '# Branch %s\n', branch)
1575 fm.write('node', '# Node ID %s\n', hex(node))
1575 fm.write('node', '# Node ID %s\n', hex(node))
1576 fm.plain('# Parent %s\n' % hex(prev))
1576 fm.plain('# Parent %s\n' % hex(prev))
1577 if len(parents) > 1:
1577 if len(parents) > 1:
1578 fm.plain('# Parent %s\n' % hex(parents[1]))
1578 fm.plain('# Parent %s\n' % hex(parents[1]))
1579 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name='node'))
1579 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name='node'))
1580
1580
1581 # TODO: redesign extraexportmap function to support formatter
1581 # TODO: redesign extraexportmap function to support formatter
1582 for headerid in extraexport:
1582 for headerid in extraexport:
1583 header = extraexportmap[headerid](seqno, ctx)
1583 header = extraexportmap[headerid](seqno, ctx)
1584 if header is not None:
1584 if header is not None:
1585 fm.plain('# %s\n' % header)
1585 fm.plain('# %s\n' % header)
1586
1586
1587 fm.write('desc', '%s\n', ctx.description().rstrip())
1587 fm.write('desc', '%s\n', ctx.description().rstrip())
1588 fm.plain('\n')
1588 fm.plain('\n')
1589
1589
1590 if fm.isplain():
1590 if fm.isplain():
1591 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1591 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1592 for chunk, label in chunkiter:
1592 for chunk, label in chunkiter:
1593 fm.plain(chunk, label=label)
1593 fm.plain(chunk, label=label)
1594 else:
1594 else:
1595 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1595 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1596 # TODO: make it structured?
1596 # TODO: make it structured?
1597 fm.data(diff=b''.join(chunkiter))
1597 fm.data(diff=b''.join(chunkiter))
1598
1598
1599 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1599 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1600 """Export changesets to stdout or a single file"""
1600 """Export changesets to stdout or a single file"""
1601 for seqno, rev in enumerate(revs, 1):
1601 for seqno, rev in enumerate(revs, 1):
1602 ctx = repo[rev]
1602 ctx = repo[rev]
1603 if not dest.startswith('<'):
1603 if not dest.startswith('<'):
1604 repo.ui.note("%s\n" % dest)
1604 repo.ui.note("%s\n" % dest)
1605 fm.startitem()
1605 fm.startitem()
1606 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1606 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1607
1607
1608 def _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, diffopts,
1608 def _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, diffopts,
1609 match):
1609 match):
1610 """Export changesets to possibly multiple files"""
1610 """Export changesets to possibly multiple files"""
1611 total = len(revs)
1611 total = len(revs)
1612 revwidth = max(len(str(rev)) for rev in revs)
1612 revwidth = max(len(str(rev)) for rev in revs)
1613 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1613 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1614
1614
1615 for seqno, rev in enumerate(revs, 1):
1615 for seqno, rev in enumerate(revs, 1):
1616 ctx = repo[rev]
1616 ctx = repo[rev]
1617 dest = makefilename(ctx, fntemplate,
1617 dest = makefilename(ctx, fntemplate,
1618 total=total, seqno=seqno, revwidth=revwidth)
1618 total=total, seqno=seqno, revwidth=revwidth)
1619 filemap.setdefault(dest, []).append((seqno, rev))
1619 filemap.setdefault(dest, []).append((seqno, rev))
1620
1620
1621 for dest in filemap:
1621 for dest in filemap:
1622 with formatter.maybereopen(basefm, dest) as fm:
1622 with formatter.maybereopen(basefm, dest) as fm:
1623 repo.ui.note("%s\n" % dest)
1623 repo.ui.note("%s\n" % dest)
1624 for seqno, rev in filemap[dest]:
1624 for seqno, rev in filemap[dest]:
1625 fm.startitem()
1625 fm.startitem()
1626 ctx = repo[rev]
1626 ctx = repo[rev]
1627 _exportsingle(repo, ctx, fm, match, switch_parent, seqno,
1627 _exportsingle(repo, ctx, fm, match, switch_parent, seqno,
1628 diffopts)
1628 diffopts)
1629
1629
1630 def export(repo, revs, basefm, fntemplate='hg-%h.patch', switch_parent=False,
1630 def export(repo, revs, basefm, fntemplate='hg-%h.patch', switch_parent=False,
1631 opts=None, match=None):
1631 opts=None, match=None):
1632 '''export changesets as hg patches
1632 '''export changesets as hg patches
1633
1633
1634 Args:
1634 Args:
1635 repo: The repository from which we're exporting revisions.
1635 repo: The repository from which we're exporting revisions.
1636 revs: A list of revisions to export as revision numbers.
1636 revs: A list of revisions to export as revision numbers.
1637 basefm: A formatter to which patches should be written.
1637 basefm: A formatter to which patches should be written.
1638 fntemplate: An optional string to use for generating patch file names.
1638 fntemplate: An optional string to use for generating patch file names.
1639 switch_parent: If True, show diffs against second parent when not nullid.
1639 switch_parent: If True, show diffs against second parent when not nullid.
1640 Default is false, which always shows diff against p1.
1640 Default is false, which always shows diff against p1.
1641 opts: diff options to use for generating the patch.
1641 opts: diff options to use for generating the patch.
1642 match: If specified, only export changes to files matching this matcher.
1642 match: If specified, only export changes to files matching this matcher.
1643
1643
1644 Returns:
1644 Returns:
1645 Nothing.
1645 Nothing.
1646
1646
1647 Side Effect:
1647 Side Effect:
1648 "HG Changeset Patch" data is emitted to one of the following
1648 "HG Changeset Patch" data is emitted to one of the following
1649 destinations:
1649 destinations:
1650 fntemplate specified: Each rev is written to a unique file named using
1650 fntemplate specified: Each rev is written to a unique file named using
1651 the given template.
1651 the given template.
1652 Otherwise: All revs will be written to basefm.
1652 Otherwise: All revs will be written to basefm.
1653 '''
1653 '''
1654 scmutil.prefetchfiles(repo, revs, match)
1654 scmutil.prefetchfiles(repo, revs, match)
1655
1655
1656 if not fntemplate:
1656 if not fntemplate:
1657 _exportfile(repo, revs, basefm, '<unnamed>', switch_parent, opts, match)
1657 _exportfile(repo, revs, basefm, '<unnamed>', switch_parent, opts, match)
1658 else:
1658 else:
1659 _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, opts,
1659 _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, opts,
1660 match)
1660 match)
1661
1661
1662 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
1662 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
1663 """Export changesets to the given file stream"""
1663 """Export changesets to the given file stream"""
1664 scmutil.prefetchfiles(repo, revs, match)
1664 scmutil.prefetchfiles(repo, revs, match)
1665
1665
1666 dest = getattr(fp, 'name', '<unnamed>')
1666 dest = getattr(fp, 'name', '<unnamed>')
1667 with formatter.formatter(repo.ui, fp, 'export', {}) as fm:
1667 with formatter.formatter(repo.ui, fp, 'export', {}) as fm:
1668 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
1668 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
1669
1669
1670 def showmarker(fm, marker, index=None):
1670 def showmarker(fm, marker, index=None):
1671 """utility function to display obsolescence marker in a readable way
1671 """utility function to display obsolescence marker in a readable way
1672
1672
1673 To be used by debug function."""
1673 To be used by debug function."""
1674 if index is not None:
1674 if index is not None:
1675 fm.write('index', '%i ', index)
1675 fm.write('index', '%i ', index)
1676 fm.write('prednode', '%s ', hex(marker.prednode()))
1676 fm.write('prednode', '%s ', hex(marker.prednode()))
1677 succs = marker.succnodes()
1677 succs = marker.succnodes()
1678 fm.condwrite(succs, 'succnodes', '%s ',
1678 fm.condwrite(succs, 'succnodes', '%s ',
1679 fm.formatlist(map(hex, succs), name='node'))
1679 fm.formatlist(map(hex, succs), name='node'))
1680 fm.write('flag', '%X ', marker.flags())
1680 fm.write('flag', '%X ', marker.flags())
1681 parents = marker.parentnodes()
1681 parents = marker.parentnodes()
1682 if parents is not None:
1682 if parents is not None:
1683 fm.write('parentnodes', '{%s} ',
1683 fm.write('parentnodes', '{%s} ',
1684 fm.formatlist(map(hex, parents), name='node', sep=', '))
1684 fm.formatlist(map(hex, parents), name='node', sep=', '))
1685 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1685 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1686 meta = marker.metadata().copy()
1686 meta = marker.metadata().copy()
1687 meta.pop('date', None)
1687 meta.pop('date', None)
1688 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
1688 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
1689 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1689 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1690 fm.plain('\n')
1690 fm.plain('\n')
1691
1691
1692 def finddate(ui, repo, date):
1692 def finddate(ui, repo, date):
1693 """Find the tipmost changeset that matches the given date spec"""
1693 """Find the tipmost changeset that matches the given date spec"""
1694
1694
1695 df = dateutil.matchdate(date)
1695 df = dateutil.matchdate(date)
1696 m = scmutil.matchall(repo)
1696 m = scmutil.matchall(repo)
1697 results = {}
1697 results = {}
1698
1698
1699 def prep(ctx, fns):
1699 def prep(ctx, fns):
1700 d = ctx.date()
1700 d = ctx.date()
1701 if df(d[0]):
1701 if df(d[0]):
1702 results[ctx.rev()] = d
1702 results[ctx.rev()] = d
1703
1703
1704 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1704 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1705 rev = ctx.rev()
1705 rev = ctx.rev()
1706 if rev in results:
1706 if rev in results:
1707 ui.status(_("found revision %s from %s\n") %
1707 ui.status(_("found revision %s from %s\n") %
1708 (rev, dateutil.datestr(results[rev])))
1708 (rev, dateutil.datestr(results[rev])))
1709 return '%d' % rev
1709 return '%d' % rev
1710
1710
1711 raise error.Abort(_("revision matching date not found"))
1711 raise error.Abort(_("revision matching date not found"))
1712
1712
1713 def increasingwindows(windowsize=8, sizelimit=512):
1713 def increasingwindows(windowsize=8, sizelimit=512):
1714 while True:
1714 while True:
1715 yield windowsize
1715 yield windowsize
1716 if windowsize < sizelimit:
1716 if windowsize < sizelimit:
1717 windowsize *= 2
1717 windowsize *= 2
1718
1718
1719 def _walkrevs(repo, opts):
1719 def _walkrevs(repo, opts):
1720 # Default --rev value depends on --follow but --follow behavior
1720 # Default --rev value depends on --follow but --follow behavior
1721 # depends on revisions resolved from --rev...
1721 # depends on revisions resolved from --rev...
1722 follow = opts.get('follow') or opts.get('follow_first')
1722 follow = opts.get('follow') or opts.get('follow_first')
1723 if opts.get('rev'):
1723 if opts.get('rev'):
1724 revs = scmutil.revrange(repo, opts['rev'])
1724 revs = scmutil.revrange(repo, opts['rev'])
1725 elif follow and repo.dirstate.p1() == nullid:
1725 elif follow and repo.dirstate.p1() == nullid:
1726 revs = smartset.baseset()
1726 revs = smartset.baseset()
1727 elif follow:
1727 elif follow:
1728 revs = repo.revs('reverse(:.)')
1728 revs = repo.revs('reverse(:.)')
1729 else:
1729 else:
1730 revs = smartset.spanset(repo)
1730 revs = smartset.spanset(repo)
1731 revs.reverse()
1731 revs.reverse()
1732 return revs
1732 return revs
1733
1733
1734 class FileWalkError(Exception):
1734 class FileWalkError(Exception):
1735 pass
1735 pass
1736
1736
1737 def walkfilerevs(repo, match, follow, revs, fncache):
1737 def walkfilerevs(repo, match, follow, revs, fncache):
1738 '''Walks the file history for the matched files.
1738 '''Walks the file history for the matched files.
1739
1739
1740 Returns the changeset revs that are involved in the file history.
1740 Returns the changeset revs that are involved in the file history.
1741
1741
1742 Throws FileWalkError if the file history can't be walked using
1742 Throws FileWalkError if the file history can't be walked using
1743 filelogs alone.
1743 filelogs alone.
1744 '''
1744 '''
1745 wanted = set()
1745 wanted = set()
1746 copies = []
1746 copies = []
1747 minrev, maxrev = min(revs), max(revs)
1747 minrev, maxrev = min(revs), max(revs)
1748 def filerevgen(filelog, last):
1748 def filerevgen(filelog, last):
1749 """
1749 """
1750 Only files, no patterns. Check the history of each file.
1750 Only files, no patterns. Check the history of each file.
1751
1751
1752 Examines filelog entries within minrev, maxrev linkrev range
1752 Examines filelog entries within minrev, maxrev linkrev range
1753 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1753 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1754 tuples in backwards order
1754 tuples in backwards order
1755 """
1755 """
1756 cl_count = len(repo)
1756 cl_count = len(repo)
1757 revs = []
1757 revs = []
1758 for j in pycompat.xrange(0, last + 1):
1758 for j in pycompat.xrange(0, last + 1):
1759 linkrev = filelog.linkrev(j)
1759 linkrev = filelog.linkrev(j)
1760 if linkrev < minrev:
1760 if linkrev < minrev:
1761 continue
1761 continue
1762 # only yield rev for which we have the changelog, it can
1762 # only yield rev for which we have the changelog, it can
1763 # happen while doing "hg log" during a pull or commit
1763 # happen while doing "hg log" during a pull or commit
1764 if linkrev >= cl_count:
1764 if linkrev >= cl_count:
1765 break
1765 break
1766
1766
1767 parentlinkrevs = []
1767 parentlinkrevs = []
1768 for p in filelog.parentrevs(j):
1768 for p in filelog.parentrevs(j):
1769 if p != nullrev:
1769 if p != nullrev:
1770 parentlinkrevs.append(filelog.linkrev(p))
1770 parentlinkrevs.append(filelog.linkrev(p))
1771 n = filelog.node(j)
1771 n = filelog.node(j)
1772 revs.append((linkrev, parentlinkrevs,
1772 revs.append((linkrev, parentlinkrevs,
1773 follow and filelog.renamed(n)))
1773 follow and filelog.renamed(n)))
1774
1774
1775 return reversed(revs)
1775 return reversed(revs)
1776 def iterfiles():
1776 def iterfiles():
1777 pctx = repo['.']
1777 pctx = repo['.']
1778 for filename in match.files():
1778 for filename in match.files():
1779 if follow:
1779 if follow:
1780 if filename not in pctx:
1780 if filename not in pctx:
1781 raise error.Abort(_('cannot follow file not in parent '
1781 raise error.Abort(_('cannot follow file not in parent '
1782 'revision: "%s"') % filename)
1782 'revision: "%s"') % filename)
1783 yield filename, pctx[filename].filenode()
1783 yield filename, pctx[filename].filenode()
1784 else:
1784 else:
1785 yield filename, None
1785 yield filename, None
1786 for filename_node in copies:
1786 for filename_node in copies:
1787 yield filename_node
1787 yield filename_node
1788
1788
1789 for file_, node in iterfiles():
1789 for file_, node in iterfiles():
1790 filelog = repo.file(file_)
1790 filelog = repo.file(file_)
1791 if not len(filelog):
1791 if not len(filelog):
1792 if node is None:
1792 if node is None:
1793 # A zero count may be a directory or deleted file, so
1793 # A zero count may be a directory or deleted file, so
1794 # try to find matching entries on the slow path.
1794 # try to find matching entries on the slow path.
1795 if follow:
1795 if follow:
1796 raise error.Abort(
1796 raise error.Abort(
1797 _('cannot follow nonexistent file: "%s"') % file_)
1797 _('cannot follow nonexistent file: "%s"') % file_)
1798 raise FileWalkError("Cannot walk via filelog")
1798 raise FileWalkError("Cannot walk via filelog")
1799 else:
1799 else:
1800 continue
1800 continue
1801
1801
1802 if node is None:
1802 if node is None:
1803 last = len(filelog) - 1
1803 last = len(filelog) - 1
1804 else:
1804 else:
1805 last = filelog.rev(node)
1805 last = filelog.rev(node)
1806
1806
1807 # keep track of all ancestors of the file
1807 # keep track of all ancestors of the file
1808 ancestors = {filelog.linkrev(last)}
1808 ancestors = {filelog.linkrev(last)}
1809
1809
1810 # iterate from latest to oldest revision
1810 # iterate from latest to oldest revision
1811 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1811 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1812 if not follow:
1812 if not follow:
1813 if rev > maxrev:
1813 if rev > maxrev:
1814 continue
1814 continue
1815 else:
1815 else:
1816 # Note that last might not be the first interesting
1816 # Note that last might not be the first interesting
1817 # rev to us:
1817 # rev to us:
1818 # if the file has been changed after maxrev, we'll
1818 # if the file has been changed after maxrev, we'll
1819 # have linkrev(last) > maxrev, and we still need
1819 # have linkrev(last) > maxrev, and we still need
1820 # to explore the file graph
1820 # to explore the file graph
1821 if rev not in ancestors:
1821 if rev not in ancestors:
1822 continue
1822 continue
1823 # XXX insert 1327 fix here
1823 # XXX insert 1327 fix here
1824 if flparentlinkrevs:
1824 if flparentlinkrevs:
1825 ancestors.update(flparentlinkrevs)
1825 ancestors.update(flparentlinkrevs)
1826
1826
1827 fncache.setdefault(rev, []).append(file_)
1827 fncache.setdefault(rev, []).append(file_)
1828 wanted.add(rev)
1828 wanted.add(rev)
1829 if copied:
1829 if copied:
1830 copies.append(copied)
1830 copies.append(copied)
1831
1831
1832 return wanted
1832 return wanted
1833
1833
1834 class _followfilter(object):
1834 class _followfilter(object):
1835 def __init__(self, repo, onlyfirst=False):
1835 def __init__(self, repo, onlyfirst=False):
1836 self.repo = repo
1836 self.repo = repo
1837 self.startrev = nullrev
1837 self.startrev = nullrev
1838 self.roots = set()
1838 self.roots = set()
1839 self.onlyfirst = onlyfirst
1839 self.onlyfirst = onlyfirst
1840
1840
1841 def match(self, rev):
1841 def match(self, rev):
1842 def realparents(rev):
1842 def realparents(rev):
1843 if self.onlyfirst:
1843 if self.onlyfirst:
1844 return self.repo.changelog.parentrevs(rev)[0:1]
1844 return self.repo.changelog.parentrevs(rev)[0:1]
1845 else:
1845 else:
1846 return filter(lambda x: x != nullrev,
1846 return filter(lambda x: x != nullrev,
1847 self.repo.changelog.parentrevs(rev))
1847 self.repo.changelog.parentrevs(rev))
1848
1848
1849 if self.startrev == nullrev:
1849 if self.startrev == nullrev:
1850 self.startrev = rev
1850 self.startrev = rev
1851 return True
1851 return True
1852
1852
1853 if rev > self.startrev:
1853 if rev > self.startrev:
1854 # forward: all descendants
1854 # forward: all descendants
1855 if not self.roots:
1855 if not self.roots:
1856 self.roots.add(self.startrev)
1856 self.roots.add(self.startrev)
1857 for parent in realparents(rev):
1857 for parent in realparents(rev):
1858 if parent in self.roots:
1858 if parent in self.roots:
1859 self.roots.add(rev)
1859 self.roots.add(rev)
1860 return True
1860 return True
1861 else:
1861 else:
1862 # backwards: all parents
1862 # backwards: all parents
1863 if not self.roots:
1863 if not self.roots:
1864 self.roots.update(realparents(self.startrev))
1864 self.roots.update(realparents(self.startrev))
1865 if rev in self.roots:
1865 if rev in self.roots:
1866 self.roots.remove(rev)
1866 self.roots.remove(rev)
1867 self.roots.update(realparents(rev))
1867 self.roots.update(realparents(rev))
1868 return True
1868 return True
1869
1869
1870 return False
1870 return False
1871
1871
1872 def walkchangerevs(repo, match, opts, prepare):
1872 def walkchangerevs(repo, match, opts, prepare):
1873 '''Iterate over files and the revs in which they changed.
1873 '''Iterate over files and the revs in which they changed.
1874
1874
1875 Callers most commonly need to iterate backwards over the history
1875 Callers most commonly need to iterate backwards over the history
1876 in which they are interested. Doing so has awful (quadratic-looking)
1876 in which they are interested. Doing so has awful (quadratic-looking)
1877 performance, so we use iterators in a "windowed" way.
1877 performance, so we use iterators in a "windowed" way.
1878
1878
1879 We walk a window of revisions in the desired order. Within the
1879 We walk a window of revisions in the desired order. Within the
1880 window, we first walk forwards to gather data, then in the desired
1880 window, we first walk forwards to gather data, then in the desired
1881 order (usually backwards) to display it.
1881 order (usually backwards) to display it.
1882
1882
1883 This function returns an iterator yielding contexts. Before
1883 This function returns an iterator yielding contexts. Before
1884 yielding each context, the iterator will first call the prepare
1884 yielding each context, the iterator will first call the prepare
1885 function on each context in the window in forward order.'''
1885 function on each context in the window in forward order.'''
1886
1886
1887 allfiles = opts.get('all_files')
1887 allfiles = opts.get('all_files')
1888 follow = opts.get('follow') or opts.get('follow_first')
1888 follow = opts.get('follow') or opts.get('follow_first')
1889 revs = _walkrevs(repo, opts)
1889 revs = _walkrevs(repo, opts)
1890 if not revs:
1890 if not revs:
1891 return []
1891 return []
1892 wanted = set()
1892 wanted = set()
1893 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1893 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1894 fncache = {}
1894 fncache = {}
1895 change = repo.__getitem__
1895 change = repo.__getitem__
1896
1896
1897 # First step is to fill wanted, the set of revisions that we want to yield.
1897 # First step is to fill wanted, the set of revisions that we want to yield.
1898 # When it does not induce extra cost, we also fill fncache for revisions in
1898 # When it does not induce extra cost, we also fill fncache for revisions in
1899 # wanted: a cache of filenames that were changed (ctx.files()) and that
1899 # wanted: a cache of filenames that were changed (ctx.files()) and that
1900 # match the file filtering conditions.
1900 # match the file filtering conditions.
1901
1901
1902 if match.always() or allfiles:
1902 if match.always() or allfiles:
1903 # No files, no patterns. Display all revs.
1903 # No files, no patterns. Display all revs.
1904 wanted = revs
1904 wanted = revs
1905 elif not slowpath:
1905 elif not slowpath:
1906 # We only have to read through the filelog to find wanted revisions
1906 # We only have to read through the filelog to find wanted revisions
1907
1907
1908 try:
1908 try:
1909 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1909 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1910 except FileWalkError:
1910 except FileWalkError:
1911 slowpath = True
1911 slowpath = True
1912
1912
1913 # We decided to fall back to the slowpath because at least one
1913 # We decided to fall back to the slowpath because at least one
1914 # of the paths was not a file. Check to see if at least one of them
1914 # of the paths was not a file. Check to see if at least one of them
1915 # existed in history, otherwise simply return
1915 # existed in history, otherwise simply return
1916 for path in match.files():
1916 for path in match.files():
1917 if path == '.' or path in repo.store:
1917 if path == '.' or path in repo.store:
1918 break
1918 break
1919 else:
1919 else:
1920 return []
1920 return []
1921
1921
1922 if slowpath:
1922 if slowpath:
1923 # We have to read the changelog to match filenames against
1923 # We have to read the changelog to match filenames against
1924 # changed files
1924 # changed files
1925
1925
1926 if follow:
1926 if follow:
1927 raise error.Abort(_('can only follow copies/renames for explicit '
1927 raise error.Abort(_('can only follow copies/renames for explicit '
1928 'filenames'))
1928 'filenames'))
1929
1929
1930 # The slow path checks files modified in every changeset.
1930 # The slow path checks files modified in every changeset.
1931 # This is really slow on large repos, so compute the set lazily.
1931 # This is really slow on large repos, so compute the set lazily.
1932 class lazywantedset(object):
1932 class lazywantedset(object):
1933 def __init__(self):
1933 def __init__(self):
1934 self.set = set()
1934 self.set = set()
1935 self.revs = set(revs)
1935 self.revs = set(revs)
1936
1936
1937 # No need to worry about locality here because it will be accessed
1937 # No need to worry about locality here because it will be accessed
1938 # in the same order as the increasing window below.
1938 # in the same order as the increasing window below.
1939 def __contains__(self, value):
1939 def __contains__(self, value):
1940 if value in self.set:
1940 if value in self.set:
1941 return True
1941 return True
1942 elif not value in self.revs:
1942 elif not value in self.revs:
1943 return False
1943 return False
1944 else:
1944 else:
1945 self.revs.discard(value)
1945 self.revs.discard(value)
1946 ctx = change(value)
1946 ctx = change(value)
1947 matches = [f for f in ctx.files() if match(f)]
1947 matches = [f for f in ctx.files() if match(f)]
1948 if matches:
1948 if matches:
1949 fncache[value] = matches
1949 fncache[value] = matches
1950 self.set.add(value)
1950 self.set.add(value)
1951 return True
1951 return True
1952 return False
1952 return False
1953
1953
1954 def discard(self, value):
1954 def discard(self, value):
1955 self.revs.discard(value)
1955 self.revs.discard(value)
1956 self.set.discard(value)
1956 self.set.discard(value)
1957
1957
1958 wanted = lazywantedset()
1958 wanted = lazywantedset()
1959
1959
1960 # it might be worthwhile to do this in the iterator if the rev range
1960 # it might be worthwhile to do this in the iterator if the rev range
1961 # is descending and the prune args are all within that range
1961 # is descending and the prune args are all within that range
1962 for rev in opts.get('prune', ()):
1962 for rev in opts.get('prune', ()):
1963 rev = repo[rev].rev()
1963 rev = repo[rev].rev()
1964 ff = _followfilter(repo)
1964 ff = _followfilter(repo)
1965 stop = min(revs[0], revs[-1])
1965 stop = min(revs[0], revs[-1])
1966 for x in pycompat.xrange(rev, stop - 1, -1):
1966 for x in pycompat.xrange(rev, stop - 1, -1):
1967 if ff.match(x):
1967 if ff.match(x):
1968 wanted = wanted - [x]
1968 wanted = wanted - [x]
1969
1969
1970 # Now that wanted is correctly initialized, we can iterate over the
1970 # Now that wanted is correctly initialized, we can iterate over the
1971 # revision range, yielding only revisions in wanted.
1971 # revision range, yielding only revisions in wanted.
1972 def iterate():
1972 def iterate():
1973 if follow and match.always():
1973 if follow and match.always():
1974 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1974 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1975 def want(rev):
1975 def want(rev):
1976 return ff.match(rev) and rev in wanted
1976 return ff.match(rev) and rev in wanted
1977 else:
1977 else:
1978 def want(rev):
1978 def want(rev):
1979 return rev in wanted
1979 return rev in wanted
1980
1980
1981 it = iter(revs)
1981 it = iter(revs)
1982 stopiteration = False
1982 stopiteration = False
1983 for windowsize in increasingwindows():
1983 for windowsize in increasingwindows():
1984 nrevs = []
1984 nrevs = []
1985 for i in pycompat.xrange(windowsize):
1985 for i in pycompat.xrange(windowsize):
1986 rev = next(it, None)
1986 rev = next(it, None)
1987 if rev is None:
1987 if rev is None:
1988 stopiteration = True
1988 stopiteration = True
1989 break
1989 break
1990 elif want(rev):
1990 elif want(rev):
1991 nrevs.append(rev)
1991 nrevs.append(rev)
1992 for rev in sorted(nrevs):
1992 for rev in sorted(nrevs):
1993 fns = fncache.get(rev)
1993 fns = fncache.get(rev)
1994 ctx = change(rev)
1994 ctx = change(rev)
1995 if not fns:
1995 if not fns:
1996 def fns_generator():
1996 def fns_generator():
1997 if allfiles:
1997 if allfiles:
1998 fiter = iter(ctx)
1998 fiter = iter(ctx)
1999 else:
1999 else:
2000 fiter = ctx.files()
2000 fiter = ctx.files()
2001 for f in fiter:
2001 for f in fiter:
2002 if match(f):
2002 if match(f):
2003 yield f
2003 yield f
2004 fns = fns_generator()
2004 fns = fns_generator()
2005 prepare(ctx, fns)
2005 prepare(ctx, fns)
2006 for rev in nrevs:
2006 for rev in nrevs:
2007 yield change(rev)
2007 yield change(rev)
2008
2008
2009 if stopiteration:
2009 if stopiteration:
2010 break
2010 break
2011
2011
2012 return iterate()
2012 return iterate()
2013
2013
2014 def add(ui, repo, match, prefix, explicitonly, **opts):
2014 def add(ui, repo, match, prefix, explicitonly, **opts):
2015 join = lambda f: os.path.join(prefix, f)
2015 join = lambda f: os.path.join(prefix, f)
2016 bad = []
2016 bad = []
2017
2017
2018 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2018 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2019 names = []
2019 names = []
2020 wctx = repo[None]
2020 wctx = repo[None]
2021 cca = None
2021 cca = None
2022 abort, warn = scmutil.checkportabilityalert(ui)
2022 abort, warn = scmutil.checkportabilityalert(ui)
2023 if abort or warn:
2023 if abort or warn:
2024 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2024 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2025
2025
2026 badmatch = matchmod.badmatch(match, badfn)
2026 badmatch = matchmod.badmatch(match, badfn)
2027 dirstate = repo.dirstate
2027 dirstate = repo.dirstate
2028 # We don't want to just call wctx.walk here, since it would return a lot of
2028 # We don't want to just call wctx.walk here, since it would return a lot of
2029 # clean files, which we aren't interested in and takes time.
2029 # clean files, which we aren't interested in and takes time.
2030 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
2030 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
2031 unknown=True, ignored=False, full=False)):
2031 unknown=True, ignored=False, full=False)):
2032 exact = match.exact(f)
2032 exact = match.exact(f)
2033 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2033 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2034 if cca:
2034 if cca:
2035 cca(f)
2035 cca(f)
2036 names.append(f)
2036 names.append(f)
2037 if ui.verbose or not exact:
2037 if ui.verbose or not exact:
2038 ui.status(_('adding %s\n') % match.rel(f))
2038 ui.status(_('adding %s\n') % match.rel(f))
2039
2039
2040 for subpath in sorted(wctx.substate):
2040 for subpath in sorted(wctx.substate):
2041 sub = wctx.sub(subpath)
2041 sub = wctx.sub(subpath)
2042 try:
2042 try:
2043 submatch = matchmod.subdirmatcher(subpath, match)
2043 submatch = matchmod.subdirmatcher(subpath, match)
2044 if opts.get(r'subrepos'):
2044 if opts.get(r'subrepos'):
2045 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2045 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2046 else:
2046 else:
2047 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2047 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2048 except error.LookupError:
2048 except error.LookupError:
2049 ui.status(_("skipping missing subrepository: %s\n")
2049 ui.status(_("skipping missing subrepository: %s\n")
2050 % join(subpath))
2050 % join(subpath))
2051
2051
2052 if not opts.get(r'dry_run'):
2052 if not opts.get(r'dry_run'):
2053 rejected = wctx.add(names, prefix)
2053 rejected = wctx.add(names, prefix)
2054 bad.extend(f for f in rejected if f in match.files())
2054 bad.extend(f for f in rejected if f in match.files())
2055 return bad
2055 return bad
2056
2056
2057 def addwebdirpath(repo, serverpath, webconf):
2057 def addwebdirpath(repo, serverpath, webconf):
2058 webconf[serverpath] = repo.root
2058 webconf[serverpath] = repo.root
2059 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2059 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2060
2060
2061 for r in repo.revs('filelog("path:.hgsub")'):
2061 for r in repo.revs('filelog("path:.hgsub")'):
2062 ctx = repo[r]
2062 ctx = repo[r]
2063 for subpath in ctx.substate:
2063 for subpath in ctx.substate:
2064 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2064 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2065
2065
2066 def forget(ui, repo, match, prefix, explicitonly, dryrun, interactive):
2066 def forget(ui, repo, match, prefix, explicitonly, dryrun, interactive):
2067 if dryrun and interactive:
2067 if dryrun and interactive:
2068 raise error.Abort(_("cannot specify both --dry-run and --interactive"))
2068 raise error.Abort(_("cannot specify both --dry-run and --interactive"))
2069 join = lambda f: os.path.join(prefix, f)
2069 join = lambda f: os.path.join(prefix, f)
2070 bad = []
2070 bad = []
2071 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2071 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2072 wctx = repo[None]
2072 wctx = repo[None]
2073 forgot = []
2073 forgot = []
2074
2074
2075 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2075 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2076 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2076 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2077 if explicitonly:
2077 if explicitonly:
2078 forget = [f for f in forget if match.exact(f)]
2078 forget = [f for f in forget if match.exact(f)]
2079
2079
2080 for subpath in sorted(wctx.substate):
2080 for subpath in sorted(wctx.substate):
2081 sub = wctx.sub(subpath)
2081 sub = wctx.sub(subpath)
2082 try:
2082 try:
2083 submatch = matchmod.subdirmatcher(subpath, match)
2083 submatch = matchmod.subdirmatcher(subpath, match)
2084 subbad, subforgot = sub.forget(submatch, prefix, dryrun=dryrun,
2084 subbad, subforgot = sub.forget(submatch, prefix, dryrun=dryrun,
2085 interactive=interactive)
2085 interactive=interactive)
2086 bad.extend([subpath + '/' + f for f in subbad])
2086 bad.extend([subpath + '/' + f for f in subbad])
2087 forgot.extend([subpath + '/' + f for f in subforgot])
2087 forgot.extend([subpath + '/' + f for f in subforgot])
2088 except error.LookupError:
2088 except error.LookupError:
2089 ui.status(_("skipping missing subrepository: %s\n")
2089 ui.status(_("skipping missing subrepository: %s\n")
2090 % join(subpath))
2090 % join(subpath))
2091
2091
2092 if not explicitonly:
2092 if not explicitonly:
2093 for f in match.files():
2093 for f in match.files():
2094 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2094 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2095 if f not in forgot:
2095 if f not in forgot:
2096 if repo.wvfs.exists(f):
2096 if repo.wvfs.exists(f):
2097 # Don't complain if the exact case match wasn't given.
2097 # Don't complain if the exact case match wasn't given.
2098 # But don't do this until after checking 'forgot', so
2098 # But don't do this until after checking 'forgot', so
2099 # that subrepo files aren't normalized, and this op is
2099 # that subrepo files aren't normalized, and this op is
2100 # purely from data cached by the status walk above.
2100 # purely from data cached by the status walk above.
2101 if repo.dirstate.normalize(f) in repo.dirstate:
2101 if repo.dirstate.normalize(f) in repo.dirstate:
2102 continue
2102 continue
2103 ui.warn(_('not removing %s: '
2103 ui.warn(_('not removing %s: '
2104 'file is already untracked\n')
2104 'file is already untracked\n')
2105 % match.rel(f))
2105 % match.rel(f))
2106 bad.append(f)
2106 bad.append(f)
2107
2107
2108 if interactive:
2108 if interactive:
2109 responses = _('[Ynsa?]'
2109 responses = _('[Ynsa?]'
2110 '$$ &Yes, forget this file'
2110 '$$ &Yes, forget this file'
2111 '$$ &No, skip this file'
2111 '$$ &No, skip this file'
2112 '$$ &Skip remaining files'
2112 '$$ &Skip remaining files'
2113 '$$ Include &all remaining files'
2113 '$$ Include &all remaining files'
2114 '$$ &? (display help)')
2114 '$$ &? (display help)')
2115 for filename in forget[:]:
2115 for filename in forget[:]:
2116 r = ui.promptchoice(_('forget %s %s') % (filename, responses))
2116 r = ui.promptchoice(_('forget %s %s') % (filename, responses))
2117 if r == 4: # ?
2117 if r == 4: # ?
2118 while r == 4:
2118 while r == 4:
2119 for c, t in ui.extractchoices(responses)[1]:
2119 for c, t in ui.extractchoices(responses)[1]:
2120 ui.write('%s - %s\n' % (c, encoding.lower(t)))
2120 ui.write('%s - %s\n' % (c, encoding.lower(t)))
2121 r = ui.promptchoice(_('forget %s %s') % (filename,
2121 r = ui.promptchoice(_('forget %s %s') % (filename,
2122 responses))
2122 responses))
2123 if r == 0: # yes
2123 if r == 0: # yes
2124 continue
2124 continue
2125 elif r == 1: # no
2125 elif r == 1: # no
2126 forget.remove(filename)
2126 forget.remove(filename)
2127 elif r == 2: # Skip
2127 elif r == 2: # Skip
2128 fnindex = forget.index(filename)
2128 fnindex = forget.index(filename)
2129 del forget[fnindex:]
2129 del forget[fnindex:]
2130 break
2130 break
2131 elif r == 3: # All
2131 elif r == 3: # All
2132 break
2132 break
2133
2133
2134 for f in forget:
2134 for f in forget:
2135 if ui.verbose or not match.exact(f) or interactive:
2135 if ui.verbose or not match.exact(f) or interactive:
2136 ui.status(_('removing %s\n') % match.rel(f))
2136 ui.status(_('removing %s\n') % match.rel(f))
2137
2137
2138 if not dryrun:
2138 if not dryrun:
2139 rejected = wctx.forget(forget, prefix)
2139 rejected = wctx.forget(forget, prefix)
2140 bad.extend(f for f in rejected if f in match.files())
2140 bad.extend(f for f in rejected if f in match.files())
2141 forgot.extend(f for f in forget if f not in rejected)
2141 forgot.extend(f for f in forget if f not in rejected)
2142 return bad, forgot
2142 return bad, forgot
2143
2143
2144 def files(ui, ctx, m, fm, fmt, subrepos):
2144 def files(ui, ctx, m, fm, fmt, subrepos):
2145 ret = 1
2145 ret = 1
2146
2146
2147 needsfctx = ui.verbose or {'size', 'flags'} & fm.datahint()
2147 needsfctx = ui.verbose or {'size', 'flags'} & fm.datahint()
2148 for f in ctx.matches(m):
2148 for f in ctx.matches(m):
2149 fm.startitem()
2149 fm.startitem()
2150 fm.context(ctx=ctx)
2150 fm.context(ctx=ctx)
2151 if needsfctx:
2151 if needsfctx:
2152 fc = ctx[f]
2152 fc = ctx[f]
2153 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2153 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2154 fm.data(abspath=f)
2154 fm.data(abspath=f)
2155 fm.write('path', fmt, m.rel(f))
2155 fm.write('path', fmt, m.rel(f))
2156 ret = 0
2156 ret = 0
2157
2157
2158 for subpath in sorted(ctx.substate):
2158 for subpath in sorted(ctx.substate):
2159 submatch = matchmod.subdirmatcher(subpath, m)
2159 submatch = matchmod.subdirmatcher(subpath, m)
2160 if (subrepos or m.exact(subpath) or any(submatch.files())):
2160 if (subrepos or m.exact(subpath) or any(submatch.files())):
2161 sub = ctx.sub(subpath)
2161 sub = ctx.sub(subpath)
2162 try:
2162 try:
2163 recurse = m.exact(subpath) or subrepos
2163 recurse = m.exact(subpath) or subrepos
2164 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2164 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2165 ret = 0
2165 ret = 0
2166 except error.LookupError:
2166 except error.LookupError:
2167 ui.status(_("skipping missing subrepository: %s\n")
2167 ui.status(_("skipping missing subrepository: %s\n")
2168 % m.abs(subpath))
2168 % m.abs(subpath))
2169
2169
2170 return ret
2170 return ret
2171
2171
2172 def remove(ui, repo, m, prefix, after, force, subrepos, dryrun, warnings=None):
2172 def remove(ui, repo, m, prefix, after, force, subrepos, dryrun, warnings=None):
2173 join = lambda f: os.path.join(prefix, f)
2173 join = lambda f: os.path.join(prefix, f)
2174 ret = 0
2174 ret = 0
2175 s = repo.status(match=m, clean=True)
2175 s = repo.status(match=m, clean=True)
2176 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2176 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2177
2177
2178 wctx = repo[None]
2178 wctx = repo[None]
2179
2179
2180 if warnings is None:
2180 if warnings is None:
2181 warnings = []
2181 warnings = []
2182 warn = True
2182 warn = True
2183 else:
2183 else:
2184 warn = False
2184 warn = False
2185
2185
2186 subs = sorted(wctx.substate)
2186 subs = sorted(wctx.substate)
2187 progress = ui.makeprogress(_('searching'), total=len(subs),
2187 progress = ui.makeprogress(_('searching'), total=len(subs),
2188 unit=_('subrepos'))
2188 unit=_('subrepos'))
2189 for subpath in subs:
2189 for subpath in subs:
2190 submatch = matchmod.subdirmatcher(subpath, m)
2190 submatch = matchmod.subdirmatcher(subpath, m)
2191 if subrepos or m.exact(subpath) or any(submatch.files()):
2191 if subrepos or m.exact(subpath) or any(submatch.files()):
2192 progress.increment()
2192 progress.increment()
2193 sub = wctx.sub(subpath)
2193 sub = wctx.sub(subpath)
2194 try:
2194 try:
2195 if sub.removefiles(submatch, prefix, after, force, subrepos,
2195 if sub.removefiles(submatch, prefix, after, force, subrepos,
2196 dryrun, warnings):
2196 dryrun, warnings):
2197 ret = 1
2197 ret = 1
2198 except error.LookupError:
2198 except error.LookupError:
2199 warnings.append(_("skipping missing subrepository: %s\n")
2199 warnings.append(_("skipping missing subrepository: %s\n")
2200 % join(subpath))
2200 % join(subpath))
2201 progress.complete()
2201 progress.complete()
2202
2202
2203 # warn about failure to delete explicit files/dirs
2203 # warn about failure to delete explicit files/dirs
2204 deleteddirs = util.dirs(deleted)
2204 deleteddirs = util.dirs(deleted)
2205 files = m.files()
2205 files = m.files()
2206 progress = ui.makeprogress(_('deleting'), total=len(files),
2206 progress = ui.makeprogress(_('deleting'), total=len(files),
2207 unit=_('files'))
2207 unit=_('files'))
2208 for f in files:
2208 for f in files:
2209 def insubrepo():
2209 def insubrepo():
2210 for subpath in wctx.substate:
2210 for subpath in wctx.substate:
2211 if f.startswith(subpath + '/'):
2211 if f.startswith(subpath + '/'):
2212 return True
2212 return True
2213 return False
2213 return False
2214
2214
2215 progress.increment()
2215 progress.increment()
2216 isdir = f in deleteddirs or wctx.hasdir(f)
2216 isdir = f in deleteddirs or wctx.hasdir(f)
2217 if (f in repo.dirstate or isdir or f == '.'
2217 if (f in repo.dirstate or isdir or f == '.'
2218 or insubrepo() or f in subs):
2218 or insubrepo() or f in subs):
2219 continue
2219 continue
2220
2220
2221 if repo.wvfs.exists(f):
2221 if repo.wvfs.exists(f):
2222 if repo.wvfs.isdir(f):
2222 if repo.wvfs.isdir(f):
2223 warnings.append(_('not removing %s: no tracked files\n')
2223 warnings.append(_('not removing %s: no tracked files\n')
2224 % m.rel(f))
2224 % m.rel(f))
2225 else:
2225 else:
2226 warnings.append(_('not removing %s: file is untracked\n')
2226 warnings.append(_('not removing %s: file is untracked\n')
2227 % m.rel(f))
2227 % m.rel(f))
2228 # missing files will generate a warning elsewhere
2228 # missing files will generate a warning elsewhere
2229 ret = 1
2229 ret = 1
2230 progress.complete()
2230 progress.complete()
2231
2231
2232 if force:
2232 if force:
2233 list = modified + deleted + clean + added
2233 list = modified + deleted + clean + added
2234 elif after:
2234 elif after:
2235 list = deleted
2235 list = deleted
2236 remaining = modified + added + clean
2236 remaining = modified + added + clean
2237 progress = ui.makeprogress(_('skipping'), total=len(remaining),
2237 progress = ui.makeprogress(_('skipping'), total=len(remaining),
2238 unit=_('files'))
2238 unit=_('files'))
2239 for f in remaining:
2239 for f in remaining:
2240 progress.increment()
2240 progress.increment()
2241 if ui.verbose or (f in files):
2241 if ui.verbose or (f in files):
2242 warnings.append(_('not removing %s: file still exists\n')
2242 warnings.append(_('not removing %s: file still exists\n')
2243 % m.rel(f))
2243 % m.rel(f))
2244 ret = 1
2244 ret = 1
2245 progress.complete()
2245 progress.complete()
2246 else:
2246 else:
2247 list = deleted + clean
2247 list = deleted + clean
2248 progress = ui.makeprogress(_('skipping'),
2248 progress = ui.makeprogress(_('skipping'),
2249 total=(len(modified) + len(added)),
2249 total=(len(modified) + len(added)),
2250 unit=_('files'))
2250 unit=_('files'))
2251 for f in modified:
2251 for f in modified:
2252 progress.increment()
2252 progress.increment()
2253 warnings.append(_('not removing %s: file is modified (use -f'
2253 warnings.append(_('not removing %s: file is modified (use -f'
2254 ' to force removal)\n') % m.rel(f))
2254 ' to force removal)\n') % m.rel(f))
2255 ret = 1
2255 ret = 1
2256 for f in added:
2256 for f in added:
2257 progress.increment()
2257 progress.increment()
2258 warnings.append(_("not removing %s: file has been marked for add"
2258 warnings.append(_("not removing %s: file has been marked for add"
2259 " (use 'hg forget' to undo add)\n") % m.rel(f))
2259 " (use 'hg forget' to undo add)\n") % m.rel(f))
2260 ret = 1
2260 ret = 1
2261 progress.complete()
2261 progress.complete()
2262
2262
2263 list = sorted(list)
2263 list = sorted(list)
2264 progress = ui.makeprogress(_('deleting'), total=len(list),
2264 progress = ui.makeprogress(_('deleting'), total=len(list),
2265 unit=_('files'))
2265 unit=_('files'))
2266 for f in list:
2266 for f in list:
2267 if ui.verbose or not m.exact(f):
2267 if ui.verbose or not m.exact(f):
2268 progress.increment()
2268 progress.increment()
2269 ui.status(_('removing %s\n') % m.rel(f))
2269 ui.status(_('removing %s\n') % m.rel(f))
2270 progress.complete()
2270 progress.complete()
2271
2271
2272 if not dryrun:
2272 if not dryrun:
2273 with repo.wlock():
2273 with repo.wlock():
2274 if not after:
2274 if not after:
2275 for f in list:
2275 for f in list:
2276 if f in added:
2276 if f in added:
2277 continue # we never unlink added files on remove
2277 continue # we never unlink added files on remove
2278 rmdir = repo.ui.configbool('experimental',
2278 rmdir = repo.ui.configbool('experimental',
2279 'removeemptydirs')
2279 'removeemptydirs')
2280 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2280 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2281 repo[None].forget(list)
2281 repo[None].forget(list)
2282
2282
2283 if warn:
2283 if warn:
2284 for warning in warnings:
2284 for warning in warnings:
2285 ui.warn(warning)
2285 ui.warn(warning)
2286
2286
2287 return ret
2287 return ret
2288
2288
2289 def _updatecatformatter(fm, ctx, matcher, path, decode):
2289 def _updatecatformatter(fm, ctx, matcher, path, decode):
2290 """Hook for adding data to the formatter used by ``hg cat``.
2290 """Hook for adding data to the formatter used by ``hg cat``.
2291
2291
2292 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2292 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2293 this method first."""
2293 this method first."""
2294 data = ctx[path].data()
2294 data = ctx[path].data()
2295 if decode:
2295 if decode:
2296 data = ctx.repo().wwritedata(path, data)
2296 data = ctx.repo().wwritedata(path, data)
2297 fm.startitem()
2297 fm.startitem()
2298 fm.context(ctx=ctx)
2298 fm.context(ctx=ctx)
2299 fm.write('data', '%s', data)
2299 fm.write('data', '%s', data)
2300 fm.data(abspath=path, path=matcher.rel(path))
2300 fm.data(abspath=path, path=matcher.rel(path))
2301
2301
2302 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2302 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2303 err = 1
2303 err = 1
2304 opts = pycompat.byteskwargs(opts)
2304 opts = pycompat.byteskwargs(opts)
2305
2305
2306 def write(path):
2306 def write(path):
2307 filename = None
2307 filename = None
2308 if fntemplate:
2308 if fntemplate:
2309 filename = makefilename(ctx, fntemplate,
2309 filename = makefilename(ctx, fntemplate,
2310 pathname=os.path.join(prefix, path))
2310 pathname=os.path.join(prefix, path))
2311 # attempt to create the directory if it does not already exist
2311 # attempt to create the directory if it does not already exist
2312 try:
2312 try:
2313 os.makedirs(os.path.dirname(filename))
2313 os.makedirs(os.path.dirname(filename))
2314 except OSError:
2314 except OSError:
2315 pass
2315 pass
2316 with formatter.maybereopen(basefm, filename) as fm:
2316 with formatter.maybereopen(basefm, filename) as fm:
2317 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2317 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2318
2318
2319 # Automation often uses hg cat on single files, so special case it
2319 # Automation often uses hg cat on single files, so special case it
2320 # for performance to avoid the cost of parsing the manifest.
2320 # for performance to avoid the cost of parsing the manifest.
2321 if len(matcher.files()) == 1 and not matcher.anypats():
2321 if len(matcher.files()) == 1 and not matcher.anypats():
2322 file = matcher.files()[0]
2322 file = matcher.files()[0]
2323 mfl = repo.manifestlog
2323 mfl = repo.manifestlog
2324 mfnode = ctx.manifestnode()
2324 mfnode = ctx.manifestnode()
2325 try:
2325 try:
2326 if mfnode and mfl[mfnode].find(file)[0]:
2326 if mfnode and mfl[mfnode].find(file)[0]:
2327 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2327 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2328 write(file)
2328 write(file)
2329 return 0
2329 return 0
2330 except KeyError:
2330 except KeyError:
2331 pass
2331 pass
2332
2332
2333 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2333 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2334
2334
2335 for abs in ctx.walk(matcher):
2335 for abs in ctx.walk(matcher):
2336 write(abs)
2336 write(abs)
2337 err = 0
2337 err = 0
2338
2338
2339 for subpath in sorted(ctx.substate):
2339 for subpath in sorted(ctx.substate):
2340 sub = ctx.sub(subpath)
2340 sub = ctx.sub(subpath)
2341 try:
2341 try:
2342 submatch = matchmod.subdirmatcher(subpath, matcher)
2342 submatch = matchmod.subdirmatcher(subpath, matcher)
2343
2343
2344 if not sub.cat(submatch, basefm, fntemplate,
2344 if not sub.cat(submatch, basefm, fntemplate,
2345 os.path.join(prefix, sub._path),
2345 os.path.join(prefix, sub._path),
2346 **pycompat.strkwargs(opts)):
2346 **pycompat.strkwargs(opts)):
2347 err = 0
2347 err = 0
2348 except error.RepoLookupError:
2348 except error.RepoLookupError:
2349 ui.status(_("skipping missing subrepository: %s\n")
2349 ui.status(_("skipping missing subrepository: %s\n")
2350 % os.path.join(prefix, subpath))
2350 % os.path.join(prefix, subpath))
2351
2351
2352 return err
2352 return err
2353
2353
2354 def commit(ui, repo, commitfunc, pats, opts):
2354 def commit(ui, repo, commitfunc, pats, opts):
2355 '''commit the specified files or all outstanding changes'''
2355 '''commit the specified files or all outstanding changes'''
2356 date = opts.get('date')
2356 date = opts.get('date')
2357 if date:
2357 if date:
2358 opts['date'] = dateutil.parsedate(date)
2358 opts['date'] = dateutil.parsedate(date)
2359 message = logmessage(ui, opts)
2359 message = logmessage(ui, opts)
2360 matcher = scmutil.match(repo[None], pats, opts)
2360 matcher = scmutil.match(repo[None], pats, opts)
2361
2361
2362 dsguard = None
2362 dsguard = None
2363 # extract addremove carefully -- this function can be called from a command
2363 # extract addremove carefully -- this function can be called from a command
2364 # that doesn't support addremove
2364 # that doesn't support addremove
2365 if opts.get('addremove'):
2365 if opts.get('addremove'):
2366 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2366 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2367 with dsguard or util.nullcontextmanager():
2367 with dsguard or util.nullcontextmanager():
2368 if dsguard:
2368 if dsguard:
2369 if scmutil.addremove(repo, matcher, "", opts) != 0:
2369 if scmutil.addremove(repo, matcher, "", opts) != 0:
2370 raise error.Abort(
2370 raise error.Abort(
2371 _("failed to mark all new/missing files as added/removed"))
2371 _("failed to mark all new/missing files as added/removed"))
2372
2372
2373 return commitfunc(ui, repo, message, matcher, opts)
2373 return commitfunc(ui, repo, message, matcher, opts)
2374
2374
2375 def samefile(f, ctx1, ctx2):
2375 def samefile(f, ctx1, ctx2):
2376 if f in ctx1.manifest():
2376 if f in ctx1.manifest():
2377 a = ctx1.filectx(f)
2377 a = ctx1.filectx(f)
2378 if f in ctx2.manifest():
2378 if f in ctx2.manifest():
2379 b = ctx2.filectx(f)
2379 b = ctx2.filectx(f)
2380 return (not a.cmp(b)
2380 return (not a.cmp(b)
2381 and a.flags() == b.flags())
2381 and a.flags() == b.flags())
2382 else:
2382 else:
2383 return False
2383 return False
2384 else:
2384 else:
2385 return f not in ctx2.manifest()
2385 return f not in ctx2.manifest()
2386
2386
2387 def amend(ui, repo, old, extra, pats, opts):
2387 def amend(ui, repo, old, extra, pats, opts):
2388 # avoid cycle context -> subrepo -> cmdutil
2388 # avoid cycle context -> subrepo -> cmdutil
2389 from . import context
2389 from . import context
2390
2390
2391 # amend will reuse the existing user if not specified, but the obsolete
2391 # amend will reuse the existing user if not specified, but the obsolete
2392 # marker creation requires that the current user's name is specified.
2392 # marker creation requires that the current user's name is specified.
2393 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2393 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2394 ui.username() # raise exception if username not set
2394 ui.username() # raise exception if username not set
2395
2395
2396 ui.note(_('amending changeset %s\n') % old)
2396 ui.note(_('amending changeset %s\n') % old)
2397 base = old.p1()
2397 base = old.p1()
2398
2398
2399 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2399 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2400 # Participating changesets:
2400 # Participating changesets:
2401 #
2401 #
2402 # wctx o - workingctx that contains changes from working copy
2402 # wctx o - workingctx that contains changes from working copy
2403 # | to go into amending commit
2403 # | to go into amending commit
2404 # |
2404 # |
2405 # old o - changeset to amend
2405 # old o - changeset to amend
2406 # |
2406 # |
2407 # base o - first parent of the changeset to amend
2407 # base o - first parent of the changeset to amend
2408 wctx = repo[None]
2408 wctx = repo[None]
2409
2409
2410 # Copy to avoid mutating input
2410 # Copy to avoid mutating input
2411 extra = extra.copy()
2411 extra = extra.copy()
2412 # Update extra dict from amended commit (e.g. to preserve graft
2412 # Update extra dict from amended commit (e.g. to preserve graft
2413 # source)
2413 # source)
2414 extra.update(old.extra())
2414 extra.update(old.extra())
2415
2415
2416 # Also update it from the from the wctx
2416 # Also update it from the from the wctx
2417 extra.update(wctx.extra())
2417 extra.update(wctx.extra())
2418
2418
2419 user = opts.get('user') or old.user()
2419 user = opts.get('user') or old.user()
2420 date = opts.get('date') or old.date()
2420 date = opts.get('date') or old.date()
2421
2421
2422 # Parse the date to allow comparison between date and old.date()
2422 # Parse the date to allow comparison between date and old.date()
2423 date = dateutil.parsedate(date)
2423 date = dateutil.parsedate(date)
2424
2424
2425 if len(old.parents()) > 1:
2425 if len(old.parents()) > 1:
2426 # ctx.files() isn't reliable for merges, so fall back to the
2426 # ctx.files() isn't reliable for merges, so fall back to the
2427 # slower repo.status() method
2427 # slower repo.status() method
2428 files = set([fn for st in base.status(old)[:3]
2428 files = set([fn for st in base.status(old)[:3]
2429 for fn in st])
2429 for fn in st])
2430 else:
2430 else:
2431 files = set(old.files())
2431 files = set(old.files())
2432
2432
2433 # add/remove the files to the working copy if the "addremove" option
2433 # add/remove the files to the working copy if the "addremove" option
2434 # was specified.
2434 # was specified.
2435 matcher = scmutil.match(wctx, pats, opts)
2435 matcher = scmutil.match(wctx, pats, opts)
2436 if (opts.get('addremove')
2436 if (opts.get('addremove')
2437 and scmutil.addremove(repo, matcher, "", opts)):
2437 and scmutil.addremove(repo, matcher, "", opts)):
2438 raise error.Abort(
2438 raise error.Abort(
2439 _("failed to mark all new/missing files as added/removed"))
2439 _("failed to mark all new/missing files as added/removed"))
2440
2440
2441 # Check subrepos. This depends on in-place wctx._status update in
2441 # Check subrepos. This depends on in-place wctx._status update in
2442 # subrepo.precommit(). To minimize the risk of this hack, we do
2442 # subrepo.precommit(). To minimize the risk of this hack, we do
2443 # nothing if .hgsub does not exist.
2443 # nothing if .hgsub does not exist.
2444 if '.hgsub' in wctx or '.hgsub' in old:
2444 if '.hgsub' in wctx or '.hgsub' in old:
2445 subs, commitsubs, newsubstate = subrepoutil.precommit(
2445 subs, commitsubs, newsubstate = subrepoutil.precommit(
2446 ui, wctx, wctx._status, matcher)
2446 ui, wctx, wctx._status, matcher)
2447 # amend should abort if commitsubrepos is enabled
2447 # amend should abort if commitsubrepos is enabled
2448 assert not commitsubs
2448 assert not commitsubs
2449 if subs:
2449 if subs:
2450 subrepoutil.writestate(repo, newsubstate)
2450 subrepoutil.writestate(repo, newsubstate)
2451
2451
2452 ms = mergemod.mergestate.read(repo)
2452 ms = mergemod.mergestate.read(repo)
2453 mergeutil.checkunresolved(ms)
2453 mergeutil.checkunresolved(ms)
2454
2454
2455 filestoamend = set(f for f in wctx.files() if matcher(f))
2455 filestoamend = set(f for f in wctx.files() if matcher(f))
2456
2456
2457 changes = (len(filestoamend) > 0)
2457 changes = (len(filestoamend) > 0)
2458 if changes:
2458 if changes:
2459 # Recompute copies (avoid recording a -> b -> a)
2459 # Recompute copies (avoid recording a -> b -> a)
2460 copied = copies.pathcopies(base, wctx, matcher)
2460 copied = copies.pathcopies(base, wctx, matcher)
2461 if old.p2:
2461 if old.p2:
2462 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2462 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2463
2463
2464 # Prune files which were reverted by the updates: if old
2464 # Prune files which were reverted by the updates: if old
2465 # introduced file X and the file was renamed in the working
2465 # introduced file X and the file was renamed in the working
2466 # copy, then those two files are the same and
2466 # copy, then those two files are the same and
2467 # we can discard X from our list of files. Likewise if X
2467 # we can discard X from our list of files. Likewise if X
2468 # was removed, it's no longer relevant. If X is missing (aka
2468 # was removed, it's no longer relevant. If X is missing (aka
2469 # deleted), old X must be preserved.
2469 # deleted), old X must be preserved.
2470 files.update(filestoamend)
2470 files.update(filestoamend)
2471 files = [f for f in files if (not samefile(f, wctx, base)
2471 files = [f for f in files if (not samefile(f, wctx, base)
2472 or f in wctx.deleted())]
2472 or f in wctx.deleted())]
2473
2473
2474 def filectxfn(repo, ctx_, path):
2474 def filectxfn(repo, ctx_, path):
2475 try:
2475 try:
2476 # If the file being considered is not amongst the files
2476 # If the file being considered is not amongst the files
2477 # to be amended, we should return the file context from the
2477 # to be amended, we should return the file context from the
2478 # old changeset. This avoids issues when only some files in
2478 # old changeset. This avoids issues when only some files in
2479 # the working copy are being amended but there are also
2479 # the working copy are being amended but there are also
2480 # changes to other files from the old changeset.
2480 # changes to other files from the old changeset.
2481 if path not in filestoamend:
2481 if path not in filestoamend:
2482 return old.filectx(path)
2482 return old.filectx(path)
2483
2483
2484 # Return None for removed files.
2484 # Return None for removed files.
2485 if path in wctx.removed():
2485 if path in wctx.removed():
2486 return None
2486 return None
2487
2487
2488 fctx = wctx[path]
2488 fctx = wctx[path]
2489 flags = fctx.flags()
2489 flags = fctx.flags()
2490 mctx = context.memfilectx(repo, ctx_,
2490 mctx = context.memfilectx(repo, ctx_,
2491 fctx.path(), fctx.data(),
2491 fctx.path(), fctx.data(),
2492 islink='l' in flags,
2492 islink='l' in flags,
2493 isexec='x' in flags,
2493 isexec='x' in flags,
2494 copied=copied.get(path))
2494 copied=copied.get(path))
2495 return mctx
2495 return mctx
2496 except KeyError:
2496 except KeyError:
2497 return None
2497 return None
2498 else:
2498 else:
2499 ui.note(_('copying changeset %s to %s\n') % (old, base))
2499 ui.note(_('copying changeset %s to %s\n') % (old, base))
2500
2500
2501 # Use version of files as in the old cset
2501 # Use version of files as in the old cset
2502 def filectxfn(repo, ctx_, path):
2502 def filectxfn(repo, ctx_, path):
2503 try:
2503 try:
2504 return old.filectx(path)
2504 return old.filectx(path)
2505 except KeyError:
2505 except KeyError:
2506 return None
2506 return None
2507
2507
2508 # See if we got a message from -m or -l, if not, open the editor with
2508 # See if we got a message from -m or -l, if not, open the editor with
2509 # the message of the changeset to amend.
2509 # the message of the changeset to amend.
2510 message = logmessage(ui, opts)
2510 message = logmessage(ui, opts)
2511
2511
2512 editform = mergeeditform(old, 'commit.amend')
2512 editform = mergeeditform(old, 'commit.amend')
2513 editor = getcommiteditor(editform=editform,
2513 editor = getcommiteditor(editform=editform,
2514 **pycompat.strkwargs(opts))
2514 **pycompat.strkwargs(opts))
2515
2515
2516 if not message:
2516 if not message:
2517 editor = getcommiteditor(edit=True, editform=editform)
2517 editor = getcommiteditor(edit=True, editform=editform)
2518 message = old.description()
2518 message = old.description()
2519
2519
2520 pureextra = extra.copy()
2520 pureextra = extra.copy()
2521 extra['amend_source'] = old.hex()
2521 extra['amend_source'] = old.hex()
2522
2522
2523 new = context.memctx(repo,
2523 new = context.memctx(repo,
2524 parents=[base.node(), old.p2().node()],
2524 parents=[base.node(), old.p2().node()],
2525 text=message,
2525 text=message,
2526 files=files,
2526 files=files,
2527 filectxfn=filectxfn,
2527 filectxfn=filectxfn,
2528 user=user,
2528 user=user,
2529 date=date,
2529 date=date,
2530 extra=extra,
2530 extra=extra,
2531 editor=editor)
2531 editor=editor)
2532
2532
2533 newdesc = changelog.stripdesc(new.description())
2533 newdesc = changelog.stripdesc(new.description())
2534 if ((not changes)
2534 if ((not changes)
2535 and newdesc == old.description()
2535 and newdesc == old.description()
2536 and user == old.user()
2536 and user == old.user()
2537 and date == old.date()
2537 and date == old.date()
2538 and pureextra == old.extra()):
2538 and pureextra == old.extra()):
2539 # nothing changed. continuing here would create a new node
2539 # nothing changed. continuing here would create a new node
2540 # anyway because of the amend_source noise.
2540 # anyway because of the amend_source noise.
2541 #
2541 #
2542 # This not what we expect from amend.
2542 # This not what we expect from amend.
2543 return old.node()
2543 return old.node()
2544
2544
2545 commitphase = None
2545 commitphase = None
2546 if opts.get('secret'):
2546 if opts.get('secret'):
2547 commitphase = phases.secret
2547 commitphase = phases.secret
2548 newid = repo.commitctx(new)
2548 newid = repo.commitctx(new)
2549
2549
2550 # Reroute the working copy parent to the new changeset
2550 # Reroute the working copy parent to the new changeset
2551 repo.setparents(newid, nullid)
2551 repo.setparents(newid, nullid)
2552 mapping = {old.node(): (newid,)}
2552 mapping = {old.node(): (newid,)}
2553 obsmetadata = None
2553 obsmetadata = None
2554 if opts.get('note'):
2554 if opts.get('note'):
2555 obsmetadata = {'note': encoding.fromlocal(opts['note'])}
2555 obsmetadata = {'note': encoding.fromlocal(opts['note'])}
2556 backup = ui.configbool('ui', 'history-editing-backup')
2556 backup = ui.configbool('ui', 'history-editing-backup')
2557 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata,
2557 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata,
2558 fixphase=True, targetphase=commitphase,
2558 fixphase=True, targetphase=commitphase,
2559 backup=backup)
2559 backup=backup)
2560
2560
2561 # Fixing the dirstate because localrepo.commitctx does not update
2561 # Fixing the dirstate because localrepo.commitctx does not update
2562 # it. This is rather convenient because we did not need to update
2562 # it. This is rather convenient because we did not need to update
2563 # the dirstate for all the files in the new commit which commitctx
2563 # the dirstate for all the files in the new commit which commitctx
2564 # could have done if it updated the dirstate. Now, we can
2564 # could have done if it updated the dirstate. Now, we can
2565 # selectively update the dirstate only for the amended files.
2565 # selectively update the dirstate only for the amended files.
2566 dirstate = repo.dirstate
2566 dirstate = repo.dirstate
2567
2567
2568 # Update the state of the files which were added and
2568 # Update the state of the files which were added and
2569 # and modified in the amend to "normal" in the dirstate.
2569 # and modified in the amend to "normal" in the dirstate.
2570 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2570 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2571 for f in normalfiles:
2571 for f in normalfiles:
2572 dirstate.normal(f)
2572 dirstate.normal(f)
2573
2573
2574 # Update the state of files which were removed in the amend
2574 # Update the state of files which were removed in the amend
2575 # to "removed" in the dirstate.
2575 # to "removed" in the dirstate.
2576 removedfiles = set(wctx.removed()) & filestoamend
2576 removedfiles = set(wctx.removed()) & filestoamend
2577 for f in removedfiles:
2577 for f in removedfiles:
2578 dirstate.drop(f)
2578 dirstate.drop(f)
2579
2579
2580 return newid
2580 return newid
2581
2581
2582 def commiteditor(repo, ctx, subs, editform=''):
2582 def commiteditor(repo, ctx, subs, editform=''):
2583 if ctx.description():
2583 if ctx.description():
2584 return ctx.description()
2584 return ctx.description()
2585 return commitforceeditor(repo, ctx, subs, editform=editform,
2585 return commitforceeditor(repo, ctx, subs, editform=editform,
2586 unchangedmessagedetection=True)
2586 unchangedmessagedetection=True)
2587
2587
2588 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2588 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2589 editform='', unchangedmessagedetection=False):
2589 editform='', unchangedmessagedetection=False):
2590 if not extramsg:
2590 if not extramsg:
2591 extramsg = _("Leave message empty to abort commit.")
2591 extramsg = _("Leave message empty to abort commit.")
2592
2592
2593 forms = [e for e in editform.split('.') if e]
2593 forms = [e for e in editform.split('.') if e]
2594 forms.insert(0, 'changeset')
2594 forms.insert(0, 'changeset')
2595 templatetext = None
2595 templatetext = None
2596 while forms:
2596 while forms:
2597 ref = '.'.join(forms)
2597 ref = '.'.join(forms)
2598 if repo.ui.config('committemplate', ref):
2598 if repo.ui.config('committemplate', ref):
2599 templatetext = committext = buildcommittemplate(
2599 templatetext = committext = buildcommittemplate(
2600 repo, ctx, subs, extramsg, ref)
2600 repo, ctx, subs, extramsg, ref)
2601 break
2601 break
2602 forms.pop()
2602 forms.pop()
2603 else:
2603 else:
2604 committext = buildcommittext(repo, ctx, subs, extramsg)
2604 committext = buildcommittext(repo, ctx, subs, extramsg)
2605
2605
2606 # run editor in the repository root
2606 # run editor in the repository root
2607 olddir = pycompat.getcwd()
2607 olddir = pycompat.getcwd()
2608 os.chdir(repo.root)
2608 os.chdir(repo.root)
2609
2609
2610 # make in-memory changes visible to external process
2610 # make in-memory changes visible to external process
2611 tr = repo.currenttransaction()
2611 tr = repo.currenttransaction()
2612 repo.dirstate.write(tr)
2612 repo.dirstate.write(tr)
2613 pending = tr and tr.writepending() and repo.root
2613 pending = tr and tr.writepending() and repo.root
2614
2614
2615 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2615 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2616 editform=editform, pending=pending,
2616 editform=editform, pending=pending,
2617 repopath=repo.path, action='commit')
2617 repopath=repo.path, action='commit')
2618 text = editortext
2618 text = editortext
2619
2619
2620 # strip away anything below this special string (used for editors that want
2620 # strip away anything below this special string (used for editors that want
2621 # to display the diff)
2621 # to display the diff)
2622 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2622 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2623 if stripbelow:
2623 if stripbelow:
2624 text = text[:stripbelow.start()]
2624 text = text[:stripbelow.start()]
2625
2625
2626 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2626 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2627 os.chdir(olddir)
2627 os.chdir(olddir)
2628
2628
2629 if finishdesc:
2629 if finishdesc:
2630 text = finishdesc(text)
2630 text = finishdesc(text)
2631 if not text.strip():
2631 if not text.strip():
2632 raise error.Abort(_("empty commit message"))
2632 raise error.Abort(_("empty commit message"))
2633 if unchangedmessagedetection and editortext == templatetext:
2633 if unchangedmessagedetection and editortext == templatetext:
2634 raise error.Abort(_("commit message unchanged"))
2634 raise error.Abort(_("commit message unchanged"))
2635
2635
2636 return text
2636 return text
2637
2637
2638 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2638 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2639 ui = repo.ui
2639 ui = repo.ui
2640 spec = formatter.templatespec(ref, None, None)
2640 spec = formatter.templatespec(ref, None, None)
2641 t = logcmdutil.changesettemplater(ui, repo, spec)
2641 t = logcmdutil.changesettemplater(ui, repo, spec)
2642 t.t.cache.update((k, templater.unquotestring(v))
2642 t.t.cache.update((k, templater.unquotestring(v))
2643 for k, v in repo.ui.configitems('committemplate'))
2643 for k, v in repo.ui.configitems('committemplate'))
2644
2644
2645 if not extramsg:
2645 if not extramsg:
2646 extramsg = '' # ensure that extramsg is string
2646 extramsg = '' # ensure that extramsg is string
2647
2647
2648 ui.pushbuffer()
2648 ui.pushbuffer()
2649 t.show(ctx, extramsg=extramsg)
2649 t.show(ctx, extramsg=extramsg)
2650 return ui.popbuffer()
2650 return ui.popbuffer()
2651
2651
2652 def hgprefix(msg):
2652 def hgprefix(msg):
2653 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2653 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2654
2654
2655 def buildcommittext(repo, ctx, subs, extramsg):
2655 def buildcommittext(repo, ctx, subs, extramsg):
2656 edittext = []
2656 edittext = []
2657 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2657 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2658 if ctx.description():
2658 if ctx.description():
2659 edittext.append(ctx.description())
2659 edittext.append(ctx.description())
2660 edittext.append("")
2660 edittext.append("")
2661 edittext.append("") # Empty line between message and comments.
2661 edittext.append("") # Empty line between message and comments.
2662 edittext.append(hgprefix(_("Enter commit message."
2662 edittext.append(hgprefix(_("Enter commit message."
2663 " Lines beginning with 'HG:' are removed.")))
2663 " Lines beginning with 'HG:' are removed.")))
2664 edittext.append(hgprefix(extramsg))
2664 edittext.append(hgprefix(extramsg))
2665 edittext.append("HG: --")
2665 edittext.append("HG: --")
2666 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2666 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2667 if ctx.p2():
2667 if ctx.p2():
2668 edittext.append(hgprefix(_("branch merge")))
2668 edittext.append(hgprefix(_("branch merge")))
2669 if ctx.branch():
2669 if ctx.branch():
2670 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2670 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2671 if bookmarks.isactivewdirparent(repo):
2671 if bookmarks.isactivewdirparent(repo):
2672 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2672 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2673 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2673 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2674 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2674 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2675 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2675 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2676 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2676 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2677 if not added and not modified and not removed:
2677 if not added and not modified and not removed:
2678 edittext.append(hgprefix(_("no files changed")))
2678 edittext.append(hgprefix(_("no files changed")))
2679 edittext.append("")
2679 edittext.append("")
2680
2680
2681 return "\n".join(edittext)
2681 return "\n".join(edittext)
2682
2682
2683 def commitstatus(repo, node, branch, bheads=None, opts=None):
2683 def commitstatus(repo, node, branch, bheads=None, opts=None):
2684 if opts is None:
2684 if opts is None:
2685 opts = {}
2685 opts = {}
2686 ctx = repo[node]
2686 ctx = repo[node]
2687 parents = ctx.parents()
2687 parents = ctx.parents()
2688
2688
2689 if (not opts.get('amend') and bheads and node not in bheads and not
2689 if (not opts.get('amend') and bheads and node not in bheads and not
2690 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2690 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2691 repo.ui.status(_('created new head\n'))
2691 repo.ui.status(_('created new head\n'))
2692 # The message is not printed for initial roots. For the other
2692 # The message is not printed for initial roots. For the other
2693 # changesets, it is printed in the following situations:
2693 # changesets, it is printed in the following situations:
2694 #
2694 #
2695 # Par column: for the 2 parents with ...
2695 # Par column: for the 2 parents with ...
2696 # N: null or no parent
2696 # N: null or no parent
2697 # B: parent is on another named branch
2697 # B: parent is on another named branch
2698 # C: parent is a regular non head changeset
2698 # C: parent is a regular non head changeset
2699 # H: parent was a branch head of the current branch
2699 # H: parent was a branch head of the current branch
2700 # Msg column: whether we print "created new head" message
2700 # Msg column: whether we print "created new head" message
2701 # In the following, it is assumed that there already exists some
2701 # In the following, it is assumed that there already exists some
2702 # initial branch heads of the current branch, otherwise nothing is
2702 # initial branch heads of the current branch, otherwise nothing is
2703 # printed anyway.
2703 # printed anyway.
2704 #
2704 #
2705 # Par Msg Comment
2705 # Par Msg Comment
2706 # N N y additional topo root
2706 # N N y additional topo root
2707 #
2707 #
2708 # B N y additional branch root
2708 # B N y additional branch root
2709 # C N y additional topo head
2709 # C N y additional topo head
2710 # H N n usual case
2710 # H N n usual case
2711 #
2711 #
2712 # B B y weird additional branch root
2712 # B B y weird additional branch root
2713 # C B y branch merge
2713 # C B y branch merge
2714 # H B n merge with named branch
2714 # H B n merge with named branch
2715 #
2715 #
2716 # C C y additional head from merge
2716 # C C y additional head from merge
2717 # C H n merge with a head
2717 # C H n merge with a head
2718 #
2718 #
2719 # H H n head merge: head count decreases
2719 # H H n head merge: head count decreases
2720
2720
2721 if not opts.get('close_branch'):
2721 if not opts.get('close_branch'):
2722 for r in parents:
2722 for r in parents:
2723 if r.closesbranch() and r.branch() == branch:
2723 if r.closesbranch() and r.branch() == branch:
2724 repo.ui.status(_('reopening closed branch head %d\n') % r.rev())
2724 repo.ui.status(_('reopening closed branch head %d\n') % r.rev())
2725
2725
2726 if repo.ui.debugflag:
2726 if repo.ui.debugflag:
2727 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex()))
2727 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex()))
2728 elif repo.ui.verbose:
2728 elif repo.ui.verbose:
2729 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx))
2729 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx))
2730
2730
2731 def postcommitstatus(repo, pats, opts):
2731 def postcommitstatus(repo, pats, opts):
2732 return repo.status(match=scmutil.match(repo[None], pats, opts))
2732 return repo.status(match=scmutil.match(repo[None], pats, opts))
2733
2733
2734 def revert(ui, repo, ctx, parents, *pats, **opts):
2734 def revert(ui, repo, ctx, parents, *pats, **opts):
2735 opts = pycompat.byteskwargs(opts)
2735 opts = pycompat.byteskwargs(opts)
2736 parent, p2 = parents
2736 parent, p2 = parents
2737 node = ctx.node()
2737 node = ctx.node()
2738
2738
2739 mf = ctx.manifest()
2739 mf = ctx.manifest()
2740 if node == p2:
2740 if node == p2:
2741 parent = p2
2741 parent = p2
2742
2742
2743 # need all matching names in dirstate and manifest of target rev,
2743 # need all matching names in dirstate and manifest of target rev,
2744 # so have to walk both. do not print errors if files exist in one
2744 # so have to walk both. do not print errors if files exist in one
2745 # but not other. in both cases, filesets should be evaluated against
2745 # but not other. in both cases, filesets should be evaluated against
2746 # workingctx to get consistent result (issue4497). this means 'set:**'
2746 # workingctx to get consistent result (issue4497). this means 'set:**'
2747 # cannot be used to select missing files from target rev.
2747 # cannot be used to select missing files from target rev.
2748
2748
2749 # `names` is a mapping for all elements in working copy and target revision
2749 # `names` is a mapping for all elements in working copy and target revision
2750 # The mapping is in the form:
2750 # The mapping is in the form:
2751 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2751 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2752 names = {}
2752 names = {}
2753
2753
2754 with repo.wlock():
2754 with repo.wlock():
2755 ## filling of the `names` mapping
2755 ## filling of the `names` mapping
2756 # walk dirstate to fill `names`
2756 # walk dirstate to fill `names`
2757
2757
2758 interactive = opts.get('interactive', False)
2758 interactive = opts.get('interactive', False)
2759 wctx = repo[None]
2759 wctx = repo[None]
2760 m = scmutil.match(wctx, pats, opts)
2760 m = scmutil.match(wctx, pats, opts)
2761
2761
2762 # we'll need this later
2762 # we'll need this later
2763 targetsubs = sorted(s for s in wctx.substate if m(s))
2763 targetsubs = sorted(s for s in wctx.substate if m(s))
2764
2764
2765 if not m.always():
2765 if not m.always():
2766 matcher = matchmod.badmatch(m, lambda x, y: False)
2766 matcher = matchmod.badmatch(m, lambda x, y: False)
2767 for abs in wctx.walk(matcher):
2767 for abs in wctx.walk(matcher):
2768 names[abs] = m.rel(abs), m.exact(abs)
2768 names[abs] = m.rel(abs), m.exact(abs)
2769
2769
2770 # walk target manifest to fill `names`
2770 # walk target manifest to fill `names`
2771
2771
2772 def badfn(path, msg):
2772 def badfn(path, msg):
2773 if path in names:
2773 if path in names:
2774 return
2774 return
2775 if path in ctx.substate:
2775 if path in ctx.substate:
2776 return
2776 return
2777 path_ = path + '/'
2777 path_ = path + '/'
2778 for f in names:
2778 for f in names:
2779 if f.startswith(path_):
2779 if f.startswith(path_):
2780 return
2780 return
2781 ui.warn("%s: %s\n" % (m.rel(path), msg))
2781 ui.warn("%s: %s\n" % (m.rel(path), msg))
2782
2782
2783 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2783 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2784 if abs not in names:
2784 if abs not in names:
2785 names[abs] = m.rel(abs), m.exact(abs)
2785 names[abs] = m.rel(abs), m.exact(abs)
2786
2786
2787 # Find status of all file in `names`.
2787 # Find status of all file in `names`.
2788 m = scmutil.matchfiles(repo, names)
2788 m = scmutil.matchfiles(repo, names)
2789
2789
2790 changes = repo.status(node1=node, match=m,
2790 changes = repo.status(node1=node, match=m,
2791 unknown=True, ignored=True, clean=True)
2791 unknown=True, ignored=True, clean=True)
2792 else:
2792 else:
2793 changes = repo.status(node1=node, match=m)
2793 changes = repo.status(node1=node, match=m)
2794 for kind in changes:
2794 for kind in changes:
2795 for abs in kind:
2795 for abs in kind:
2796 names[abs] = m.rel(abs), m.exact(abs)
2796 names[abs] = m.rel(abs), m.exact(abs)
2797
2797
2798 m = scmutil.matchfiles(repo, names)
2798 m = scmutil.matchfiles(repo, names)
2799
2799
2800 modified = set(changes.modified)
2800 modified = set(changes.modified)
2801 added = set(changes.added)
2801 added = set(changes.added)
2802 removed = set(changes.removed)
2802 removed = set(changes.removed)
2803 _deleted = set(changes.deleted)
2803 _deleted = set(changes.deleted)
2804 unknown = set(changes.unknown)
2804 unknown = set(changes.unknown)
2805 unknown.update(changes.ignored)
2805 unknown.update(changes.ignored)
2806 clean = set(changes.clean)
2806 clean = set(changes.clean)
2807 modadded = set()
2807 modadded = set()
2808
2808
2809 # We need to account for the state of the file in the dirstate,
2809 # We need to account for the state of the file in the dirstate,
2810 # even when we revert against something else than parent. This will
2810 # even when we revert against something else than parent. This will
2811 # slightly alter the behavior of revert (doing back up or not, delete
2811 # slightly alter the behavior of revert (doing back up or not, delete
2812 # or just forget etc).
2812 # or just forget etc).
2813 if parent == node:
2813 if parent == node:
2814 dsmodified = modified
2814 dsmodified = modified
2815 dsadded = added
2815 dsadded = added
2816 dsremoved = removed
2816 dsremoved = removed
2817 # store all local modifications, useful later for rename detection
2817 # store all local modifications, useful later for rename detection
2818 localchanges = dsmodified | dsadded
2818 localchanges = dsmodified | dsadded
2819 modified, added, removed = set(), set(), set()
2819 modified, added, removed = set(), set(), set()
2820 else:
2820 else:
2821 changes = repo.status(node1=parent, match=m)
2821 changes = repo.status(node1=parent, match=m)
2822 dsmodified = set(changes.modified)
2822 dsmodified = set(changes.modified)
2823 dsadded = set(changes.added)
2823 dsadded = set(changes.added)
2824 dsremoved = set(changes.removed)
2824 dsremoved = set(changes.removed)
2825 # store all local modifications, useful later for rename detection
2825 # store all local modifications, useful later for rename detection
2826 localchanges = dsmodified | dsadded
2826 localchanges = dsmodified | dsadded
2827
2827
2828 # only take into account for removes between wc and target
2828 # only take into account for removes between wc and target
2829 clean |= dsremoved - removed
2829 clean |= dsremoved - removed
2830 dsremoved &= removed
2830 dsremoved &= removed
2831 # distinct between dirstate remove and other
2831 # distinct between dirstate remove and other
2832 removed -= dsremoved
2832 removed -= dsremoved
2833
2833
2834 modadded = added & dsmodified
2834 modadded = added & dsmodified
2835 added -= modadded
2835 added -= modadded
2836
2836
2837 # tell newly modified apart.
2837 # tell newly modified apart.
2838 dsmodified &= modified
2838 dsmodified &= modified
2839 dsmodified |= modified & dsadded # dirstate added may need backup
2839 dsmodified |= modified & dsadded # dirstate added may need backup
2840 modified -= dsmodified
2840 modified -= dsmodified
2841
2841
2842 # We need to wait for some post-processing to update this set
2842 # We need to wait for some post-processing to update this set
2843 # before making the distinction. The dirstate will be used for
2843 # before making the distinction. The dirstate will be used for
2844 # that purpose.
2844 # that purpose.
2845 dsadded = added
2845 dsadded = added
2846
2846
2847 # in case of merge, files that are actually added can be reported as
2847 # in case of merge, files that are actually added can be reported as
2848 # modified, we need to post process the result
2848 # modified, we need to post process the result
2849 if p2 != nullid:
2849 if p2 != nullid:
2850 mergeadd = set(dsmodified)
2850 mergeadd = set(dsmodified)
2851 for path in dsmodified:
2851 for path in dsmodified:
2852 if path in mf:
2852 if path in mf:
2853 mergeadd.remove(path)
2853 mergeadd.remove(path)
2854 dsadded |= mergeadd
2854 dsadded |= mergeadd
2855 dsmodified -= mergeadd
2855 dsmodified -= mergeadd
2856
2856
2857 # if f is a rename, update `names` to also revert the source
2857 # if f is a rename, update `names` to also revert the source
2858 cwd = repo.getcwd()
2858 cwd = repo.getcwd()
2859 for f in localchanges:
2859 for f in localchanges:
2860 src = repo.dirstate.copied(f)
2860 src = repo.dirstate.copied(f)
2861 # XXX should we check for rename down to target node?
2861 # XXX should we check for rename down to target node?
2862 if src and src not in names and repo.dirstate[src] == 'r':
2862 if src and src not in names and repo.dirstate[src] == 'r':
2863 dsremoved.add(src)
2863 dsremoved.add(src)
2864 names[src] = (repo.pathto(src, cwd), True)
2864 names[src] = (repo.pathto(src, cwd), True)
2865
2865
2866 # determine the exact nature of the deleted changesets
2866 # determine the exact nature of the deleted changesets
2867 deladded = set(_deleted)
2867 deladded = set(_deleted)
2868 for path in _deleted:
2868 for path in _deleted:
2869 if path in mf:
2869 if path in mf:
2870 deladded.remove(path)
2870 deladded.remove(path)
2871 deleted = _deleted - deladded
2871 deleted = _deleted - deladded
2872
2872
2873 # distinguish between file to forget and the other
2873 # distinguish between file to forget and the other
2874 added = set()
2874 added = set()
2875 for abs in dsadded:
2875 for abs in dsadded:
2876 if repo.dirstate[abs] != 'a':
2876 if repo.dirstate[abs] != 'a':
2877 added.add(abs)
2877 added.add(abs)
2878 dsadded -= added
2878 dsadded -= added
2879
2879
2880 for abs in deladded:
2880 for abs in deladded:
2881 if repo.dirstate[abs] == 'a':
2881 if repo.dirstate[abs] == 'a':
2882 dsadded.add(abs)
2882 dsadded.add(abs)
2883 deladded -= dsadded
2883 deladded -= dsadded
2884
2884
2885 # For files marked as removed, we check if an unknown file is present at
2885 # For files marked as removed, we check if an unknown file is present at
2886 # the same path. If a such file exists it may need to be backed up.
2886 # the same path. If a such file exists it may need to be backed up.
2887 # Making the distinction at this stage helps have simpler backup
2887 # Making the distinction at this stage helps have simpler backup
2888 # logic.
2888 # logic.
2889 removunk = set()
2889 removunk = set()
2890 for abs in removed:
2890 for abs in removed:
2891 target = repo.wjoin(abs)
2891 target = repo.wjoin(abs)
2892 if os.path.lexists(target):
2892 if os.path.lexists(target):
2893 removunk.add(abs)
2893 removunk.add(abs)
2894 removed -= removunk
2894 removed -= removunk
2895
2895
2896 dsremovunk = set()
2896 dsremovunk = set()
2897 for abs in dsremoved:
2897 for abs in dsremoved:
2898 target = repo.wjoin(abs)
2898 target = repo.wjoin(abs)
2899 if os.path.lexists(target):
2899 if os.path.lexists(target):
2900 dsremovunk.add(abs)
2900 dsremovunk.add(abs)
2901 dsremoved -= dsremovunk
2901 dsremoved -= dsremovunk
2902
2902
2903 # action to be actually performed by revert
2903 # action to be actually performed by revert
2904 # (<list of file>, message>) tuple
2904 # (<list of file>, message>) tuple
2905 actions = {'revert': ([], _('reverting %s\n')),
2905 actions = {'revert': ([], _('reverting %s\n')),
2906 'add': ([], _('adding %s\n')),
2906 'add': ([], _('adding %s\n')),
2907 'remove': ([], _('removing %s\n')),
2907 'remove': ([], _('removing %s\n')),
2908 'drop': ([], _('removing %s\n')),
2908 'drop': ([], _('removing %s\n')),
2909 'forget': ([], _('forgetting %s\n')),
2909 'forget': ([], _('forgetting %s\n')),
2910 'undelete': ([], _('undeleting %s\n')),
2910 'undelete': ([], _('undeleting %s\n')),
2911 'noop': (None, _('no changes needed to %s\n')),
2911 'noop': (None, _('no changes needed to %s\n')),
2912 'unknown': (None, _('file not managed: %s\n')),
2912 'unknown': (None, _('file not managed: %s\n')),
2913 }
2913 }
2914
2914
2915 # "constant" that convey the backup strategy.
2915 # "constant" that convey the backup strategy.
2916 # All set to `discard` if `no-backup` is set do avoid checking
2916 # All set to `discard` if `no-backup` is set do avoid checking
2917 # no_backup lower in the code.
2917 # no_backup lower in the code.
2918 # These values are ordered for comparison purposes
2918 # These values are ordered for comparison purposes
2919 backupinteractive = 3 # do backup if interactively modified
2919 backupinteractive = 3 # do backup if interactively modified
2920 backup = 2 # unconditionally do backup
2920 backup = 2 # unconditionally do backup
2921 check = 1 # check if the existing file differs from target
2921 check = 1 # check if the existing file differs from target
2922 discard = 0 # never do backup
2922 discard = 0 # never do backup
2923 if opts.get('no_backup'):
2923 if opts.get('no_backup'):
2924 backupinteractive = backup = check = discard
2924 backupinteractive = backup = check = discard
2925 if interactive:
2925 if interactive:
2926 dsmodifiedbackup = backupinteractive
2926 dsmodifiedbackup = backupinteractive
2927 else:
2927 else:
2928 dsmodifiedbackup = backup
2928 dsmodifiedbackup = backup
2929 tobackup = set()
2929 tobackup = set()
2930
2930
2931 backupanddel = actions['remove']
2931 backupanddel = actions['remove']
2932 if not opts.get('no_backup'):
2932 if not opts.get('no_backup'):
2933 backupanddel = actions['drop']
2933 backupanddel = actions['drop']
2934
2934
2935 disptable = (
2935 disptable = (
2936 # dispatch table:
2936 # dispatch table:
2937 # file state
2937 # file state
2938 # action
2938 # action
2939 # make backup
2939 # make backup
2940
2940
2941 ## Sets that results that will change file on disk
2941 ## Sets that results that will change file on disk
2942 # Modified compared to target, no local change
2942 # Modified compared to target, no local change
2943 (modified, actions['revert'], discard),
2943 (modified, actions['revert'], discard),
2944 # Modified compared to target, but local file is deleted
2944 # Modified compared to target, but local file is deleted
2945 (deleted, actions['revert'], discard),
2945 (deleted, actions['revert'], discard),
2946 # Modified compared to target, local change
2946 # Modified compared to target, local change
2947 (dsmodified, actions['revert'], dsmodifiedbackup),
2947 (dsmodified, actions['revert'], dsmodifiedbackup),
2948 # Added since target
2948 # Added since target
2949 (added, actions['remove'], discard),
2949 (added, actions['remove'], discard),
2950 # Added in working directory
2950 # Added in working directory
2951 (dsadded, actions['forget'], discard),
2951 (dsadded, actions['forget'], discard),
2952 # Added since target, have local modification
2952 # Added since target, have local modification
2953 (modadded, backupanddel, backup),
2953 (modadded, backupanddel, backup),
2954 # Added since target but file is missing in working directory
2954 # Added since target but file is missing in working directory
2955 (deladded, actions['drop'], discard),
2955 (deladded, actions['drop'], discard),
2956 # Removed since target, before working copy parent
2956 # Removed since target, before working copy parent
2957 (removed, actions['add'], discard),
2957 (removed, actions['add'], discard),
2958 # Same as `removed` but an unknown file exists at the same path
2958 # Same as `removed` but an unknown file exists at the same path
2959 (removunk, actions['add'], check),
2959 (removunk, actions['add'], check),
2960 # Removed since targe, marked as such in working copy parent
2960 # Removed since targe, marked as such in working copy parent
2961 (dsremoved, actions['undelete'], discard),
2961 (dsremoved, actions['undelete'], discard),
2962 # Same as `dsremoved` but an unknown file exists at the same path
2962 # Same as `dsremoved` but an unknown file exists at the same path
2963 (dsremovunk, actions['undelete'], check),
2963 (dsremovunk, actions['undelete'], check),
2964 ## the following sets does not result in any file changes
2964 ## the following sets does not result in any file changes
2965 # File with no modification
2965 # File with no modification
2966 (clean, actions['noop'], discard),
2966 (clean, actions['noop'], discard),
2967 # Existing file, not tracked anywhere
2967 # Existing file, not tracked anywhere
2968 (unknown, actions['unknown'], discard),
2968 (unknown, actions['unknown'], discard),
2969 )
2969 )
2970
2970
2971 for abs, (rel, exact) in sorted(names.items()):
2971 for abs, (rel, exact) in sorted(names.items()):
2972 # target file to be touch on disk (relative to cwd)
2972 # target file to be touch on disk (relative to cwd)
2973 target = repo.wjoin(abs)
2973 target = repo.wjoin(abs)
2974 # search the entry in the dispatch table.
2974 # search the entry in the dispatch table.
2975 # if the file is in any of these sets, it was touched in the working
2975 # if the file is in any of these sets, it was touched in the working
2976 # directory parent and we are sure it needs to be reverted.
2976 # directory parent and we are sure it needs to be reverted.
2977 for table, (xlist, msg), dobackup in disptable:
2977 for table, (xlist, msg), dobackup in disptable:
2978 if abs not in table:
2978 if abs not in table:
2979 continue
2979 continue
2980 if xlist is not None:
2980 if xlist is not None:
2981 xlist.append(abs)
2981 xlist.append(abs)
2982 if dobackup:
2982 if dobackup:
2983 # If in interactive mode, don't automatically create
2983 # If in interactive mode, don't automatically create
2984 # .orig files (issue4793)
2984 # .orig files (issue4793)
2985 if dobackup == backupinteractive:
2985 if dobackup == backupinteractive:
2986 tobackup.add(abs)
2986 tobackup.add(abs)
2987 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
2987 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
2988 bakname = scmutil.origpath(ui, repo, rel)
2988 bakname = scmutil.origpath(ui, repo, rel)
2989 ui.note(_('saving current version of %s as %s\n') %
2989 ui.note(_('saving current version of %s as %s\n') %
2990 (rel, bakname))
2990 (rel, bakname))
2991 if not opts.get('dry_run'):
2991 if not opts.get('dry_run'):
2992 if interactive:
2992 if interactive:
2993 util.copyfile(target, bakname)
2993 util.copyfile(target, bakname)
2994 else:
2994 else:
2995 util.rename(target, bakname)
2995 util.rename(target, bakname)
2996 if ui.verbose or not exact:
2996 if ui.verbose or not exact:
2997 if not isinstance(msg, bytes):
2997 if not isinstance(msg, bytes):
2998 msg = msg(abs)
2998 msg = msg(abs)
2999 ui.status(msg % rel)
2999 ui.status(msg % rel)
3000 elif exact:
3000 elif exact:
3001 ui.warn(msg % rel)
3001 ui.warn(msg % rel)
3002 break
3002 break
3003
3003
3004 if not opts.get('dry_run'):
3004 if not opts.get('dry_run'):
3005 needdata = ('revert', 'add', 'undelete')
3005 needdata = ('revert', 'add', 'undelete')
3006 oplist = [actions[name][0] for name in needdata]
3006 oplist = [actions[name][0] for name in needdata]
3007 prefetch = scmutil.prefetchfiles
3007 prefetch = scmutil.prefetchfiles
3008 matchfiles = scmutil.matchfiles
3008 matchfiles = scmutil.matchfiles
3009 prefetch(repo, [ctx.rev()],
3009 prefetch(repo, [ctx.rev()],
3010 matchfiles(repo,
3010 matchfiles(repo,
3011 [f for sublist in oplist for f in sublist]))
3011 [f for sublist in oplist for f in sublist]))
3012 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3012 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3013
3013
3014 if targetsubs:
3014 if targetsubs:
3015 # Revert the subrepos on the revert list
3015 # Revert the subrepos on the revert list
3016 for sub in targetsubs:
3016 for sub in targetsubs:
3017 try:
3017 try:
3018 wctx.sub(sub).revert(ctx.substate[sub], *pats,
3018 wctx.sub(sub).revert(ctx.substate[sub], *pats,
3019 **pycompat.strkwargs(opts))
3019 **pycompat.strkwargs(opts))
3020 except KeyError:
3020 except KeyError:
3021 raise error.Abort("subrepository '%s' does not exist in %s!"
3021 raise error.Abort("subrepository '%s' does not exist in %s!"
3022 % (sub, short(ctx.node())))
3022 % (sub, short(ctx.node())))
3023
3023
3024 def _performrevert(repo, parents, ctx, actions, interactive=False,
3024 def _performrevert(repo, parents, ctx, actions, interactive=False,
3025 tobackup=None):
3025 tobackup=None):
3026 """function that actually perform all the actions computed for revert
3026 """function that actually perform all the actions computed for revert
3027
3027
3028 This is an independent function to let extension to plug in and react to
3028 This is an independent function to let extension to plug in and react to
3029 the imminent revert.
3029 the imminent revert.
3030
3030
3031 Make sure you have the working directory locked when calling this function.
3031 Make sure you have the working directory locked when calling this function.
3032 """
3032 """
3033 parent, p2 = parents
3033 parent, p2 = parents
3034 node = ctx.node()
3034 node = ctx.node()
3035 excluded_files = []
3035 excluded_files = []
3036
3036
3037 def checkout(f):
3037 def checkout(f):
3038 fc = ctx[f]
3038 fc = ctx[f]
3039 repo.wwrite(f, fc.data(), fc.flags())
3039 repo.wwrite(f, fc.data(), fc.flags())
3040
3040
3041 def doremove(f):
3041 def doremove(f):
3042 try:
3042 try:
3043 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
3043 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
3044 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3044 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3045 except OSError:
3045 except OSError:
3046 pass
3046 pass
3047 repo.dirstate.remove(f)
3047 repo.dirstate.remove(f)
3048
3048
3049 audit_path = pathutil.pathauditor(repo.root, cached=True)
3049 audit_path = pathutil.pathauditor(repo.root, cached=True)
3050 for f in actions['forget'][0]:
3050 for f in actions['forget'][0]:
3051 if interactive:
3051 if interactive:
3052 choice = repo.ui.promptchoice(
3052 choice = repo.ui.promptchoice(
3053 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3053 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3054 if choice == 0:
3054 if choice == 0:
3055 repo.dirstate.drop(f)
3055 repo.dirstate.drop(f)
3056 else:
3056 else:
3057 excluded_files.append(f)
3057 excluded_files.append(f)
3058 else:
3058 else:
3059 repo.dirstate.drop(f)
3059 repo.dirstate.drop(f)
3060 for f in actions['remove'][0]:
3060 for f in actions['remove'][0]:
3061 audit_path(f)
3061 audit_path(f)
3062 if interactive:
3062 if interactive:
3063 choice = repo.ui.promptchoice(
3063 choice = repo.ui.promptchoice(
3064 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3064 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3065 if choice == 0:
3065 if choice == 0:
3066 doremove(f)
3066 doremove(f)
3067 else:
3067 else:
3068 excluded_files.append(f)
3068 excluded_files.append(f)
3069 else:
3069 else:
3070 doremove(f)
3070 doremove(f)
3071 for f in actions['drop'][0]:
3071 for f in actions['drop'][0]:
3072 audit_path(f)
3072 audit_path(f)
3073 repo.dirstate.remove(f)
3073 repo.dirstate.remove(f)
3074
3074
3075 normal = None
3075 normal = None
3076 if node == parent:
3076 if node == parent:
3077 # We're reverting to our parent. If possible, we'd like status
3077 # We're reverting to our parent. If possible, we'd like status
3078 # to report the file as clean. We have to use normallookup for
3078 # to report the file as clean. We have to use normallookup for
3079 # merges to avoid losing information about merged/dirty files.
3079 # merges to avoid losing information about merged/dirty files.
3080 if p2 != nullid:
3080 if p2 != nullid:
3081 normal = repo.dirstate.normallookup
3081 normal = repo.dirstate.normallookup
3082 else:
3082 else:
3083 normal = repo.dirstate.normal
3083 normal = repo.dirstate.normal
3084
3084
3085 newlyaddedandmodifiedfiles = set()
3085 newlyaddedandmodifiedfiles = set()
3086 if interactive:
3086 if interactive:
3087 # Prompt the user for changes to revert
3087 # Prompt the user for changes to revert
3088 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
3088 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
3089 m = scmutil.matchfiles(repo, torevert)
3089 m = scmutil.matchfiles(repo, torevert)
3090 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3090 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3091 diffopts.nodates = True
3091 diffopts.nodates = True
3092 diffopts.git = True
3092 diffopts.git = True
3093 operation = 'discard'
3093 operation = 'discard'
3094 reversehunks = True
3094 reversehunks = True
3095 if node != parent:
3095 if node != parent:
3096 operation = 'apply'
3096 operation = 'apply'
3097 reversehunks = False
3097 reversehunks = False
3098 if reversehunks:
3098 if reversehunks:
3099 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3099 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3100 else:
3100 else:
3101 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3101 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3102 originalchunks = patch.parsepatch(diff)
3102 originalchunks = patch.parsepatch(diff)
3103
3103
3104 try:
3104 try:
3105
3105
3106 chunks, opts = recordfilter(repo.ui, originalchunks,
3106 chunks, opts = recordfilter(repo.ui, originalchunks,
3107 operation=operation)
3107 operation=operation)
3108 if reversehunks:
3108 if reversehunks:
3109 chunks = patch.reversehunks(chunks)
3109 chunks = patch.reversehunks(chunks)
3110
3110
3111 except error.PatchError as err:
3111 except error.PatchError as err:
3112 raise error.Abort(_('error parsing patch: %s') % err)
3112 raise error.Abort(_('error parsing patch: %s') % err)
3113
3113
3114 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3114 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3115 if tobackup is None:
3115 if tobackup is None:
3116 tobackup = set()
3116 tobackup = set()
3117 # Apply changes
3117 # Apply changes
3118 fp = stringio()
3118 fp = stringio()
3119 for c in chunks:
3119 for c in chunks:
3120 # Create a backup file only if this hunk should be backed up
3120 # Create a backup file only if this hunk should be backed up
3121 if ishunk(c) and c.header.filename() in tobackup:
3121 if ishunk(c) and c.header.filename() in tobackup:
3122 abs = c.header.filename()
3122 abs = c.header.filename()
3123 target = repo.wjoin(abs)
3123 target = repo.wjoin(abs)
3124 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3124 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3125 util.copyfile(target, bakname)
3125 util.copyfile(target, bakname)
3126 tobackup.remove(abs)
3126 tobackup.remove(abs)
3127 c.write(fp)
3127 c.write(fp)
3128 dopatch = fp.tell()
3128 dopatch = fp.tell()
3129 fp.seek(0)
3129 fp.seek(0)
3130 if dopatch:
3130 if dopatch:
3131 try:
3131 try:
3132 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3132 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3133 except error.PatchError as err:
3133 except error.PatchError as err:
3134 raise error.Abort(pycompat.bytestr(err))
3134 raise error.Abort(pycompat.bytestr(err))
3135 del fp
3135 del fp
3136 else:
3136 else:
3137 for f in actions['revert'][0]:
3137 for f in actions['revert'][0]:
3138 checkout(f)
3138 checkout(f)
3139 if normal:
3139 if normal:
3140 normal(f)
3140 normal(f)
3141
3141
3142 for f in actions['add'][0]:
3142 for f in actions['add'][0]:
3143 # Don't checkout modified files, they are already created by the diff
3143 # Don't checkout modified files, they are already created by the diff
3144 if f not in newlyaddedandmodifiedfiles:
3144 if f not in newlyaddedandmodifiedfiles:
3145 checkout(f)
3145 checkout(f)
3146 repo.dirstate.add(f)
3146 repo.dirstate.add(f)
3147
3147
3148 normal = repo.dirstate.normallookup
3148 normal = repo.dirstate.normallookup
3149 if node == parent and p2 == nullid:
3149 if node == parent and p2 == nullid:
3150 normal = repo.dirstate.normal
3150 normal = repo.dirstate.normal
3151 for f in actions['undelete'][0]:
3151 for f in actions['undelete'][0]:
3152 checkout(f)
3152 checkout(f)
3153 normal(f)
3153 normal(f)
3154
3154
3155 copied = copies.pathcopies(repo[parent], ctx)
3155 copied = copies.pathcopies(repo[parent], ctx)
3156
3156
3157 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3157 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3158 if f in copied:
3158 if f in copied:
3159 repo.dirstate.copy(copied[f], f)
3159 repo.dirstate.copy(copied[f], f)
3160
3160
3161 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3161 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3162 # commands.outgoing. "missing" is "missing" of the result of
3162 # commands.outgoing. "missing" is "missing" of the result of
3163 # "findcommonoutgoing()"
3163 # "findcommonoutgoing()"
3164 outgoinghooks = util.hooks()
3164 outgoinghooks = util.hooks()
3165
3165
3166 # a list of (ui, repo) functions called by commands.summary
3166 # a list of (ui, repo) functions called by commands.summary
3167 summaryhooks = util.hooks()
3167 summaryhooks = util.hooks()
3168
3168
3169 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3169 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3170 #
3170 #
3171 # functions should return tuple of booleans below, if 'changes' is None:
3171 # functions should return tuple of booleans below, if 'changes' is None:
3172 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3172 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3173 #
3173 #
3174 # otherwise, 'changes' is a tuple of tuples below:
3174 # otherwise, 'changes' is a tuple of tuples below:
3175 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3175 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3176 # - (desturl, destbranch, destpeer, outgoing)
3176 # - (desturl, destbranch, destpeer, outgoing)
3177 summaryremotehooks = util.hooks()
3177 summaryremotehooks = util.hooks()
3178
3178
3179 # A list of state files kept by multistep operations like graft.
3179 # A list of state files kept by multistep operations like graft.
3180 # Since graft cannot be aborted, it is considered 'clearable' by update.
3180 # Since graft cannot be aborted, it is considered 'clearable' by update.
3181 # note: bisect is intentionally excluded
3181 # note: bisect is intentionally excluded
3182 # (state file, clearable, allowcommit, error, hint)
3182 # (state file, clearable, allowcommit, error, hint)
3183 unfinishedstates = [
3183 unfinishedstates = [
3184 ('graftstate', True, False, _('graft in progress'),
3184 ('graftstate', True, False, _('graft in progress'),
3185 _("use 'hg graft --continue' or 'hg graft --stop' to stop")),
3185 _("use 'hg graft --continue' or 'hg graft --stop' to stop")),
3186 ('updatestate', True, False, _('last update was interrupted'),
3186 ('updatestate', True, False, _('last update was interrupted'),
3187 _("use 'hg update' to get a consistent checkout"))
3187 _("use 'hg update' to get a consistent checkout"))
3188 ]
3188 ]
3189
3189
3190 def checkunfinished(repo, commit=False):
3190 def checkunfinished(repo, commit=False):
3191 '''Look for an unfinished multistep operation, like graft, and abort
3191 '''Look for an unfinished multistep operation, like graft, and abort
3192 if found. It's probably good to check this right before
3192 if found. It's probably good to check this right before
3193 bailifchanged().
3193 bailifchanged().
3194 '''
3194 '''
3195 # Check for non-clearable states first, so things like rebase will take
3195 # Check for non-clearable states first, so things like rebase will take
3196 # precedence over update.
3196 # precedence over update.
3197 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3197 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3198 if clearable or (commit and allowcommit):
3198 if clearable or (commit and allowcommit):
3199 continue
3199 continue
3200 if repo.vfs.exists(f):
3200 if repo.vfs.exists(f):
3201 raise error.Abort(msg, hint=hint)
3201 raise error.Abort(msg, hint=hint)
3202
3202
3203 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3203 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3204 if not clearable or (commit and allowcommit):
3204 if not clearable or (commit and allowcommit):
3205 continue
3205 continue
3206 if repo.vfs.exists(f):
3206 if repo.vfs.exists(f):
3207 raise error.Abort(msg, hint=hint)
3207 raise error.Abort(msg, hint=hint)
3208
3208
3209 def clearunfinished(repo):
3209 def clearunfinished(repo):
3210 '''Check for unfinished operations (as above), and clear the ones
3210 '''Check for unfinished operations (as above), and clear the ones
3211 that are clearable.
3211 that are clearable.
3212 '''
3212 '''
3213 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3213 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3214 if not clearable and repo.vfs.exists(f):
3214 if not clearable and repo.vfs.exists(f):
3215 raise error.Abort(msg, hint=hint)
3215 raise error.Abort(msg, hint=hint)
3216 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3216 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3217 if clearable and repo.vfs.exists(f):
3217 if clearable and repo.vfs.exists(f):
3218 util.unlink(repo.vfs.join(f))
3218 util.unlink(repo.vfs.join(f))
3219
3219
3220 afterresolvedstates = [
3220 afterresolvedstates = [
3221 ('graftstate',
3221 ('graftstate',
3222 _('hg graft --continue')),
3222 _('hg graft --continue')),
3223 ]
3223 ]
3224
3224
3225 def howtocontinue(repo):
3225 def howtocontinue(repo):
3226 '''Check for an unfinished operation and return the command to finish
3226 '''Check for an unfinished operation and return the command to finish
3227 it.
3227 it.
3228
3228
3229 afterresolvedstates tuples define a .hg/{file} and the corresponding
3229 afterresolvedstates tuples define a .hg/{file} and the corresponding
3230 command needed to finish it.
3230 command needed to finish it.
3231
3231
3232 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3232 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3233 a boolean.
3233 a boolean.
3234 '''
3234 '''
3235 contmsg = _("continue: %s")
3235 contmsg = _("continue: %s")
3236 for f, msg in afterresolvedstates:
3236 for f, msg in afterresolvedstates:
3237 if repo.vfs.exists(f):
3237 if repo.vfs.exists(f):
3238 return contmsg % msg, True
3238 return contmsg % msg, True
3239 if repo[None].dirty(missing=True, merge=False, branch=False):
3239 if repo[None].dirty(missing=True, merge=False, branch=False):
3240 return contmsg % _("hg commit"), False
3240 return contmsg % _("hg commit"), False
3241 return None, None
3241 return None, None
3242
3242
3243 def checkafterresolved(repo):
3243 def checkafterresolved(repo):
3244 '''Inform the user about the next action after completing hg resolve
3244 '''Inform the user about the next action after completing hg resolve
3245
3245
3246 If there's a matching afterresolvedstates, howtocontinue will yield
3246 If there's a matching afterresolvedstates, howtocontinue will yield
3247 repo.ui.warn as the reporter.
3247 repo.ui.warn as the reporter.
3248
3248
3249 Otherwise, it will yield repo.ui.note.
3249 Otherwise, it will yield repo.ui.note.
3250 '''
3250 '''
3251 msg, warning = howtocontinue(repo)
3251 msg, warning = howtocontinue(repo)
3252 if msg is not None:
3252 if msg is not None:
3253 if warning:
3253 if warning:
3254 repo.ui.warn("%s\n" % msg)
3254 repo.ui.warn("%s\n" % msg)
3255 else:
3255 else:
3256 repo.ui.note("%s\n" % msg)
3256 repo.ui.note("%s\n" % msg)
3257
3257
3258 def wrongtooltocontinue(repo, task):
3258 def wrongtooltocontinue(repo, task):
3259 '''Raise an abort suggesting how to properly continue if there is an
3259 '''Raise an abort suggesting how to properly continue if there is an
3260 active task.
3260 active task.
3261
3261
3262 Uses howtocontinue() to find the active task.
3262 Uses howtocontinue() to find the active task.
3263
3263
3264 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3264 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3265 a hint.
3265 a hint.
3266 '''
3266 '''
3267 after = howtocontinue(repo)
3267 after = howtocontinue(repo)
3268 hint = None
3268 hint = None
3269 if after[1]:
3269 if after[1]:
3270 hint = after[0]
3270 hint = after[0]
3271 raise error.Abort(_('no %s in progress') % task, hint=hint)
3271 raise error.Abort(_('no %s in progress') % task, hint=hint)
@@ -1,298 +1,298 b''
1 $ hg init
1 $ hg init
2 $ cat << EOF > a
2 $ cat << EOF > a
3 > Small Mathematical Series.
3 > Small Mathematical Series.
4 > One
4 > One
5 > Two
5 > Two
6 > Three
6 > Three
7 > Four
7 > Four
8 > Five
8 > Five
9 > Hop we are done.
9 > Hop we are done.
10 > EOF
10 > EOF
11 $ hg add a
11 $ hg add a
12 $ hg commit -m ancestor
12 $ hg commit -m ancestor
13 $ cat << EOF > a
13 $ cat << EOF > a
14 > Small Mathematical Series.
14 > Small Mathematical Series.
15 > 1
15 > 1
16 > 2
16 > 2
17 > 3
17 > 3
18 > 4
18 > 4
19 > 5
19 > 5
20 > Hop we are done.
20 > Hop we are done.
21 > EOF
21 > EOF
22 $ hg commit -m branch1
22 $ hg commit -m branch1
23 $ hg co 0
23 $ hg co 0
24 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
24 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
25 $ cat << EOF > a
25 $ cat << EOF > a
26 > Small Mathematical Series.
26 > Small Mathematical Series.
27 > 1
27 > 1
28 > 2
28 > 2
29 > 3
29 > 3
30 > 6
30 > 6
31 > 8
31 > 8
32 > Hop we are done.
32 > Hop we are done.
33 > EOF
33 > EOF
34 $ hg commit -m branch2
34 $ hg commit -m branch2
35 created new head
35 created new head
36
36
37 $ hg merge 1
37 $ hg merge 1
38 merging a
38 merging a
39 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
39 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
40 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
40 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
41 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
41 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
42 [1]
42 [1]
43
43
44 $ hg id
44 $ hg id
45 618808747361+c0c68e4fe667+ tip
45 618808747361+c0c68e4fe667+ tip
46
46
47 $ echo "[commands]" >> $HGRCPATH
47 $ echo "[commands]" >> $HGRCPATH
48 $ echo "status.verbose=true" >> $HGRCPATH
48 $ echo "status.verbose=true" >> $HGRCPATH
49 $ hg status
49 $ hg status
50 M a
50 M a
51 ? a.orig
51 ? a.orig
52 # The repository is in an unfinished *merge* state.
52 # The repository is in an unfinished *merge* state.
53
53
54 # Unresolved merge conflicts:
54 # Unresolved merge conflicts:
55 #
55 #
56 # a
56 # a
57 #
57 #
58 # To mark files as resolved: hg resolve --mark FILE
58 # To mark files as resolved: hg resolve --mark FILE
59
59
60 # To continue: hg commit
60 # To continue: hg commit
61 # To abort: hg update --clean . (warning: this will discard uncommitted changes)
61 # To abort: hg merge --abort
62
62
63
63
64 $ cat a
64 $ cat a
65 Small Mathematical Series.
65 Small Mathematical Series.
66 1
66 1
67 2
67 2
68 3
68 3
69 <<<<<<< working copy: 618808747361 - test: branch2
69 <<<<<<< working copy: 618808747361 - test: branch2
70 6
70 6
71 8
71 8
72 =======
72 =======
73 4
73 4
74 5
74 5
75 >>>>>>> merge rev: c0c68e4fe667 - test: branch1
75 >>>>>>> merge rev: c0c68e4fe667 - test: branch1
76 Hop we are done.
76 Hop we are done.
77
77
78 $ hg status --config commands.status.verbose=0
78 $ hg status --config commands.status.verbose=0
79 M a
79 M a
80 ? a.orig
80 ? a.orig
81
81
82 Verify custom conflict markers
82 Verify custom conflict markers
83
83
84 $ hg up -q --clean .
84 $ hg up -q --clean .
85 $ cat <<EOF >> .hg/hgrc
85 $ cat <<EOF >> .hg/hgrc
86 > [ui]
86 > [ui]
87 > mergemarkertemplate = '{author} {rev}'
87 > mergemarkertemplate = '{author} {rev}'
88 > EOF
88 > EOF
89
89
90 $ hg merge 1
90 $ hg merge 1
91 merging a
91 merging a
92 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
92 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
93 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
93 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
94 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
94 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
95 [1]
95 [1]
96
96
97 $ cat a
97 $ cat a
98 Small Mathematical Series.
98 Small Mathematical Series.
99 1
99 1
100 2
100 2
101 3
101 3
102 <<<<<<< working copy: test 2
102 <<<<<<< working copy: test 2
103 6
103 6
104 8
104 8
105 =======
105 =======
106 4
106 4
107 5
107 5
108 >>>>>>> merge rev: test 1
108 >>>>>>> merge rev: test 1
109 Hop we are done.
109 Hop we are done.
110
110
111 Verify line splitting of custom conflict marker which causes multiple lines
111 Verify line splitting of custom conflict marker which causes multiple lines
112
112
113 $ hg up -q --clean .
113 $ hg up -q --clean .
114 $ cat >> .hg/hgrc <<EOF
114 $ cat >> .hg/hgrc <<EOF
115 > [ui]
115 > [ui]
116 > mergemarkertemplate={author} {rev}\nfoo\nbar\nbaz
116 > mergemarkertemplate={author} {rev}\nfoo\nbar\nbaz
117 > EOF
117 > EOF
118
118
119 $ hg -q merge 1
119 $ hg -q merge 1
120 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
120 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
121 [1]
121 [1]
122
122
123 $ cat a
123 $ cat a
124 Small Mathematical Series.
124 Small Mathematical Series.
125 1
125 1
126 2
126 2
127 3
127 3
128 <<<<<<< working copy: test 2
128 <<<<<<< working copy: test 2
129 6
129 6
130 8
130 8
131 =======
131 =======
132 4
132 4
133 5
133 5
134 >>>>>>> merge rev: test 1
134 >>>>>>> merge rev: test 1
135 Hop we are done.
135 Hop we are done.
136
136
137 Verify line trimming of custom conflict marker using multi-byte characters
137 Verify line trimming of custom conflict marker using multi-byte characters
138
138
139 $ hg up -q --clean .
139 $ hg up -q --clean .
140 $ $PYTHON <<EOF
140 $ $PYTHON <<EOF
141 > fp = open('logfile', 'wb')
141 > fp = open('logfile', 'wb')
142 > fp.write(b'12345678901234567890123456789012345678901234567890' +
142 > fp.write(b'12345678901234567890123456789012345678901234567890' +
143 > b'1234567890') # there are 5 more columns for 80 columns
143 > b'1234567890') # there are 5 more columns for 80 columns
144 >
144 >
145 > # 2 x 4 = 8 columns, but 3 x 4 = 12 bytes
145 > # 2 x 4 = 8 columns, but 3 x 4 = 12 bytes
146 > fp.write(u'\u3042\u3044\u3046\u3048'.encode('utf-8'))
146 > fp.write(u'\u3042\u3044\u3046\u3048'.encode('utf-8'))
147 >
147 >
148 > fp.close()
148 > fp.close()
149 > EOF
149 > EOF
150 $ hg add logfile
150 $ hg add logfile
151 $ hg --encoding utf-8 commit --logfile logfile
151 $ hg --encoding utf-8 commit --logfile logfile
152
152
153 $ cat >> .hg/hgrc <<EOF
153 $ cat >> .hg/hgrc <<EOF
154 > [ui]
154 > [ui]
155 > mergemarkertemplate={desc|firstline}
155 > mergemarkertemplate={desc|firstline}
156 > EOF
156 > EOF
157
157
158 $ hg -q --encoding utf-8 merge 1
158 $ hg -q --encoding utf-8 merge 1
159 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
159 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
160 [1]
160 [1]
161
161
162 $ cat a
162 $ cat a
163 Small Mathematical Series.
163 Small Mathematical Series.
164 1
164 1
165 2
165 2
166 3
166 3
167 <<<<<<< working copy: 1234567890123456789012345678901234567890123456789012345...
167 <<<<<<< working copy: 1234567890123456789012345678901234567890123456789012345...
168 6
168 6
169 8
169 8
170 =======
170 =======
171 4
171 4
172 5
172 5
173 >>>>>>> merge rev: branch1
173 >>>>>>> merge rev: branch1
174 Hop we are done.
174 Hop we are done.
175
175
176 Verify basic conflict markers
176 Verify basic conflict markers
177
177
178 $ hg up -q --clean 2
178 $ hg up -q --clean 2
179 $ printf "\n[ui]\nmergemarkers=basic\n" >> .hg/hgrc
179 $ printf "\n[ui]\nmergemarkers=basic\n" >> .hg/hgrc
180
180
181 $ hg merge 1
181 $ hg merge 1
182 merging a
182 merging a
183 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
183 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
184 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
184 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
185 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
185 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
186 [1]
186 [1]
187
187
188 $ cat a
188 $ cat a
189 Small Mathematical Series.
189 Small Mathematical Series.
190 1
190 1
191 2
191 2
192 3
192 3
193 <<<<<<< working copy
193 <<<<<<< working copy
194 6
194 6
195 8
195 8
196 =======
196 =======
197 4
197 4
198 5
198 5
199 >>>>>>> merge rev
199 >>>>>>> merge rev
200 Hop we are done.
200 Hop we are done.
201
201
202 internal:merge3
202 internal:merge3
203
203
204 $ hg up -q --clean .
204 $ hg up -q --clean .
205
205
206 $ hg merge 1 --tool internal:merge3
206 $ hg merge 1 --tool internal:merge3
207 merging a
207 merging a
208 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
208 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
209 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
209 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
210 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
210 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
211 [1]
211 [1]
212 $ cat a
212 $ cat a
213 Small Mathematical Series.
213 Small Mathematical Series.
214 <<<<<<< working copy
214 <<<<<<< working copy
215 1
215 1
216 2
216 2
217 3
217 3
218 6
218 6
219 8
219 8
220 ||||||| base
220 ||||||| base
221 One
221 One
222 Two
222 Two
223 Three
223 Three
224 Four
224 Four
225 Five
225 Five
226 =======
226 =======
227 1
227 1
228 2
228 2
229 3
229 3
230 4
230 4
231 5
231 5
232 >>>>>>> merge rev
232 >>>>>>> merge rev
233 Hop we are done.
233 Hop we are done.
234
234
235 Add some unconflicting changes on each head, to make sure we really
235 Add some unconflicting changes on each head, to make sure we really
236 are merging, unlike :local and :other
236 are merging, unlike :local and :other
237
237
238 $ hg up -C
238 $ hg up -C
239 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
239 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
240 updated to "e0693e20f496: 123456789012345678901234567890123456789012345678901234567890????"
240 updated to "e0693e20f496: 123456789012345678901234567890123456789012345678901234567890????"
241 1 other heads for branch "default"
241 1 other heads for branch "default"
242 $ printf "\n\nEnd of file\n" >> a
242 $ printf "\n\nEnd of file\n" >> a
243 $ hg ci -m "Add some stuff at the end"
243 $ hg ci -m "Add some stuff at the end"
244 $ hg up -r 1
244 $ hg up -r 1
245 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
245 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
246 $ printf "Start of file\n\n\n" > tmp
246 $ printf "Start of file\n\n\n" > tmp
247 $ cat a >> tmp
247 $ cat a >> tmp
248 $ mv tmp a
248 $ mv tmp a
249 $ hg ci -m "Add some stuff at the beginning"
249 $ hg ci -m "Add some stuff at the beginning"
250
250
251 Now test :merge-other and :merge-local
251 Now test :merge-other and :merge-local
252
252
253 $ hg merge
253 $ hg merge
254 merging a
254 merging a
255 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
255 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
256 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
256 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
257 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
257 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
258 [1]
258 [1]
259 $ hg resolve --tool :merge-other a
259 $ hg resolve --tool :merge-other a
260 merging a
260 merging a
261 (no more unresolved files)
261 (no more unresolved files)
262 $ cat a
262 $ cat a
263 Start of file
263 Start of file
264
264
265
265
266 Small Mathematical Series.
266 Small Mathematical Series.
267 1
267 1
268 2
268 2
269 3
269 3
270 6
270 6
271 8
271 8
272 Hop we are done.
272 Hop we are done.
273
273
274
274
275 End of file
275 End of file
276
276
277 $ hg up -C
277 $ hg up -C
278 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
278 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
279 updated to "18b51d585961: Add some stuff at the beginning"
279 updated to "18b51d585961: Add some stuff at the beginning"
280 1 other heads for branch "default"
280 1 other heads for branch "default"
281 $ hg merge --tool :merge-local
281 $ hg merge --tool :merge-local
282 merging a
282 merging a
283 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
283 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
284 (branch merge, don't forget to commit)
284 (branch merge, don't forget to commit)
285 $ cat a
285 $ cat a
286 Start of file
286 Start of file
287
287
288
288
289 Small Mathematical Series.
289 Small Mathematical Series.
290 1
290 1
291 2
291 2
292 3
292 3
293 4
293 4
294 5
294 5
295 Hop we are done.
295 Hop we are done.
296
296
297
297
298 End of file
298 End of file
General Comments 0
You need to be logged in to leave comments. Login now