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