##// END OF EJS Templates
commit: don't let failed commit with --addremove update dirstate (issue5645)...
Martin von Zweigbergk -
r33619:5ac845ca stable
parent child Browse files
Show More
@@ -1,3751 +1,3761 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import itertools
11 import itertools
12 import os
12 import os
13 import re
13 import re
14 import tempfile
14 import tempfile
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 hex,
18 hex,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 short,
21 short,
22 )
22 )
23
23
24 from . import (
24 from . import (
25 bookmarks,
25 bookmarks,
26 changelog,
26 changelog,
27 copies,
27 copies,
28 crecord as crecordmod,
28 crecord as crecordmod,
29 dirstateguard,
29 encoding,
30 encoding,
30 error,
31 error,
31 formatter,
32 formatter,
32 graphmod,
33 graphmod,
33 match as matchmod,
34 match as matchmod,
34 obsolete,
35 obsolete,
35 patch,
36 patch,
36 pathutil,
37 pathutil,
37 phases,
38 phases,
38 pycompat,
39 pycompat,
39 registrar,
40 registrar,
40 revlog,
41 revlog,
41 revset,
42 revset,
42 scmutil,
43 scmutil,
43 smartset,
44 smartset,
44 templatekw,
45 templatekw,
45 templater,
46 templater,
46 util,
47 util,
47 vfs as vfsmod,
48 vfs as vfsmod,
48 )
49 )
49 stringio = util.stringio
50 stringio = util.stringio
50
51
51 # templates of common command options
52 # templates of common command options
52
53
53 dryrunopts = [
54 dryrunopts = [
54 ('n', 'dry-run', None,
55 ('n', 'dry-run', None,
55 _('do not perform actions, just print output')),
56 _('do not perform actions, just print output')),
56 ]
57 ]
57
58
58 remoteopts = [
59 remoteopts = [
59 ('e', 'ssh', '',
60 ('e', 'ssh', '',
60 _('specify ssh command to use'), _('CMD')),
61 _('specify ssh command to use'), _('CMD')),
61 ('', 'remotecmd', '',
62 ('', 'remotecmd', '',
62 _('specify hg command to run on the remote side'), _('CMD')),
63 _('specify hg command to run on the remote side'), _('CMD')),
63 ('', 'insecure', None,
64 ('', 'insecure', None,
64 _('do not verify server certificate (ignoring web.cacerts config)')),
65 _('do not verify server certificate (ignoring web.cacerts config)')),
65 ]
66 ]
66
67
67 walkopts = [
68 walkopts = [
68 ('I', 'include', [],
69 ('I', 'include', [],
69 _('include names matching the given patterns'), _('PATTERN')),
70 _('include names matching the given patterns'), _('PATTERN')),
70 ('X', 'exclude', [],
71 ('X', 'exclude', [],
71 _('exclude names matching the given patterns'), _('PATTERN')),
72 _('exclude names matching the given patterns'), _('PATTERN')),
72 ]
73 ]
73
74
74 commitopts = [
75 commitopts = [
75 ('m', 'message', '',
76 ('m', 'message', '',
76 _('use text as commit message'), _('TEXT')),
77 _('use text as commit message'), _('TEXT')),
77 ('l', 'logfile', '',
78 ('l', 'logfile', '',
78 _('read commit message from file'), _('FILE')),
79 _('read commit message from file'), _('FILE')),
79 ]
80 ]
80
81
81 commitopts2 = [
82 commitopts2 = [
82 ('d', 'date', '',
83 ('d', 'date', '',
83 _('record the specified date as commit date'), _('DATE')),
84 _('record the specified date as commit date'), _('DATE')),
84 ('u', 'user', '',
85 ('u', 'user', '',
85 _('record the specified user as committer'), _('USER')),
86 _('record the specified user as committer'), _('USER')),
86 ]
87 ]
87
88
88 # hidden for now
89 # hidden for now
89 formatteropts = [
90 formatteropts = [
90 ('T', 'template', '',
91 ('T', 'template', '',
91 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
92 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
92 ]
93 ]
93
94
94 templateopts = [
95 templateopts = [
95 ('', 'style', '',
96 ('', 'style', '',
96 _('display using template map file (DEPRECATED)'), _('STYLE')),
97 _('display using template map file (DEPRECATED)'), _('STYLE')),
97 ('T', 'template', '',
98 ('T', 'template', '',
98 _('display with template'), _('TEMPLATE')),
99 _('display with template'), _('TEMPLATE')),
99 ]
100 ]
100
101
101 logopts = [
102 logopts = [
102 ('p', 'patch', None, _('show patch')),
103 ('p', 'patch', None, _('show patch')),
103 ('g', 'git', None, _('use git extended diff format')),
104 ('g', 'git', None, _('use git extended diff format')),
104 ('l', 'limit', '',
105 ('l', 'limit', '',
105 _('limit number of changes displayed'), _('NUM')),
106 _('limit number of changes displayed'), _('NUM')),
106 ('M', 'no-merges', None, _('do not show merges')),
107 ('M', 'no-merges', None, _('do not show merges')),
107 ('', 'stat', None, _('output diffstat-style summary of changes')),
108 ('', 'stat', None, _('output diffstat-style summary of changes')),
108 ('G', 'graph', None, _("show the revision DAG")),
109 ('G', 'graph', None, _("show the revision DAG")),
109 ] + templateopts
110 ] + templateopts
110
111
111 diffopts = [
112 diffopts = [
112 ('a', 'text', None, _('treat all files as text')),
113 ('a', 'text', None, _('treat all files as text')),
113 ('g', 'git', None, _('use git extended diff format')),
114 ('g', 'git', None, _('use git extended diff format')),
114 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
115 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
115 ('', 'nodates', None, _('omit dates from diff headers'))
116 ('', 'nodates', None, _('omit dates from diff headers'))
116 ]
117 ]
117
118
118 diffwsopts = [
119 diffwsopts = [
119 ('w', 'ignore-all-space', None,
120 ('w', 'ignore-all-space', None,
120 _('ignore white space when comparing lines')),
121 _('ignore white space when comparing lines')),
121 ('b', 'ignore-space-change', None,
122 ('b', 'ignore-space-change', None,
122 _('ignore changes in the amount of white space')),
123 _('ignore changes in the amount of white space')),
123 ('B', 'ignore-blank-lines', None,
124 ('B', 'ignore-blank-lines', None,
124 _('ignore changes whose lines are all blank')),
125 _('ignore changes whose lines are all blank')),
125 ]
126 ]
126
127
127 diffopts2 = [
128 diffopts2 = [
128 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
129 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
129 ('p', 'show-function', None, _('show which function each change is in')),
130 ('p', 'show-function', None, _('show which function each change is in')),
130 ('', 'reverse', None, _('produce a diff that undoes the changes')),
131 ('', 'reverse', None, _('produce a diff that undoes the changes')),
131 ] + diffwsopts + [
132 ] + diffwsopts + [
132 ('U', 'unified', '',
133 ('U', 'unified', '',
133 _('number of lines of context to show'), _('NUM')),
134 _('number of lines of context to show'), _('NUM')),
134 ('', 'stat', None, _('output diffstat-style summary of changes')),
135 ('', 'stat', None, _('output diffstat-style summary of changes')),
135 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
136 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
136 ]
137 ]
137
138
138 mergetoolopts = [
139 mergetoolopts = [
139 ('t', 'tool', '', _('specify merge tool')),
140 ('t', 'tool', '', _('specify merge tool')),
140 ]
141 ]
141
142
142 similarityopts = [
143 similarityopts = [
143 ('s', 'similarity', '',
144 ('s', 'similarity', '',
144 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
145 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
145 ]
146 ]
146
147
147 subrepoopts = [
148 subrepoopts = [
148 ('S', 'subrepos', None,
149 ('S', 'subrepos', None,
149 _('recurse into subrepositories'))
150 _('recurse into subrepositories'))
150 ]
151 ]
151
152
152 debugrevlogopts = [
153 debugrevlogopts = [
153 ('c', 'changelog', False, _('open changelog')),
154 ('c', 'changelog', False, _('open changelog')),
154 ('m', 'manifest', False, _('open manifest')),
155 ('m', 'manifest', False, _('open manifest')),
155 ('', 'dir', '', _('open directory manifest')),
156 ('', 'dir', '', _('open directory manifest')),
156 ]
157 ]
157
158
158 # special string such that everything below this line will be ingored in the
159 # special string such that everything below this line will be ingored in the
159 # editor text
160 # editor text
160 _linebelow = "^HG: ------------------------ >8 ------------------------$"
161 _linebelow = "^HG: ------------------------ >8 ------------------------$"
161
162
162 def ishunk(x):
163 def ishunk(x):
163 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
164 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
164 return isinstance(x, hunkclasses)
165 return isinstance(x, hunkclasses)
165
166
166 def newandmodified(chunks, originalchunks):
167 def newandmodified(chunks, originalchunks):
167 newlyaddedandmodifiedfiles = set()
168 newlyaddedandmodifiedfiles = set()
168 for chunk in chunks:
169 for chunk in chunks:
169 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
170 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
170 originalchunks:
171 originalchunks:
171 newlyaddedandmodifiedfiles.add(chunk.header.filename())
172 newlyaddedandmodifiedfiles.add(chunk.header.filename())
172 return newlyaddedandmodifiedfiles
173 return newlyaddedandmodifiedfiles
173
174
174 def parsealiases(cmd):
175 def parsealiases(cmd):
175 return cmd.lstrip("^").split("|")
176 return cmd.lstrip("^").split("|")
176
177
177 def setupwrapcolorwrite(ui):
178 def setupwrapcolorwrite(ui):
178 # wrap ui.write so diff output can be labeled/colorized
179 # wrap ui.write so diff output can be labeled/colorized
179 def wrapwrite(orig, *args, **kw):
180 def wrapwrite(orig, *args, **kw):
180 label = kw.pop('label', '')
181 label = kw.pop('label', '')
181 for chunk, l in patch.difflabel(lambda: args):
182 for chunk, l in patch.difflabel(lambda: args):
182 orig(chunk, label=label + l)
183 orig(chunk, label=label + l)
183
184
184 oldwrite = ui.write
185 oldwrite = ui.write
185 def wrap(*args, **kwargs):
186 def wrap(*args, **kwargs):
186 return wrapwrite(oldwrite, *args, **kwargs)
187 return wrapwrite(oldwrite, *args, **kwargs)
187 setattr(ui, 'write', wrap)
188 setattr(ui, 'write', wrap)
188 return oldwrite
189 return oldwrite
189
190
190 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
191 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
191 if usecurses:
192 if usecurses:
192 if testfile:
193 if testfile:
193 recordfn = crecordmod.testdecorator(testfile,
194 recordfn = crecordmod.testdecorator(testfile,
194 crecordmod.testchunkselector)
195 crecordmod.testchunkselector)
195 else:
196 else:
196 recordfn = crecordmod.chunkselector
197 recordfn = crecordmod.chunkselector
197
198
198 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
199 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
199
200
200 else:
201 else:
201 return patch.filterpatch(ui, originalhunks, operation)
202 return patch.filterpatch(ui, originalhunks, operation)
202
203
203 def recordfilter(ui, originalhunks, operation=None):
204 def recordfilter(ui, originalhunks, operation=None):
204 """ Prompts the user to filter the originalhunks and return a list of
205 """ Prompts the user to filter the originalhunks and return a list of
205 selected hunks.
206 selected hunks.
206 *operation* is used for to build ui messages to indicate the user what
207 *operation* is used for to build ui messages to indicate the user what
207 kind of filtering they are doing: reverting, committing, shelving, etc.
208 kind of filtering they are doing: reverting, committing, shelving, etc.
208 (see patch.filterpatch).
209 (see patch.filterpatch).
209 """
210 """
210 usecurses = crecordmod.checkcurses(ui)
211 usecurses = crecordmod.checkcurses(ui)
211 testfile = ui.config('experimental', 'crecordtest')
212 testfile = ui.config('experimental', 'crecordtest')
212 oldwrite = setupwrapcolorwrite(ui)
213 oldwrite = setupwrapcolorwrite(ui)
213 try:
214 try:
214 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
215 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
215 testfile, operation)
216 testfile, operation)
216 finally:
217 finally:
217 ui.write = oldwrite
218 ui.write = oldwrite
218 return newchunks, newopts
219 return newchunks, newopts
219
220
220 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
221 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
221 filterfn, *pats, **opts):
222 filterfn, *pats, **opts):
222 from . import merge as mergemod
223 from . import merge as mergemod
223 opts = pycompat.byteskwargs(opts)
224 opts = pycompat.byteskwargs(opts)
224 if not ui.interactive():
225 if not ui.interactive():
225 if cmdsuggest:
226 if cmdsuggest:
226 msg = _('running non-interactively, use %s instead') % cmdsuggest
227 msg = _('running non-interactively, use %s instead') % cmdsuggest
227 else:
228 else:
228 msg = _('running non-interactively')
229 msg = _('running non-interactively')
229 raise error.Abort(msg)
230 raise error.Abort(msg)
230
231
231 # make sure username is set before going interactive
232 # make sure username is set before going interactive
232 if not opts.get('user'):
233 if not opts.get('user'):
233 ui.username() # raise exception, username not provided
234 ui.username() # raise exception, username not provided
234
235
235 def recordfunc(ui, repo, message, match, opts):
236 def recordfunc(ui, repo, message, match, opts):
236 """This is generic record driver.
237 """This is generic record driver.
237
238
238 Its job is to interactively filter local changes, and
239 Its job is to interactively filter local changes, and
239 accordingly prepare working directory into a state in which the
240 accordingly prepare working directory into a state in which the
240 job can be delegated to a non-interactive commit command such as
241 job can be delegated to a non-interactive commit command such as
241 'commit' or 'qrefresh'.
242 'commit' or 'qrefresh'.
242
243
243 After the actual job is done by non-interactive command, the
244 After the actual job is done by non-interactive command, the
244 working directory is restored to its original state.
245 working directory is restored to its original state.
245
246
246 In the end we'll record interesting changes, and everything else
247 In the end we'll record interesting changes, and everything else
247 will be left in place, so the user can continue working.
248 will be left in place, so the user can continue working.
248 """
249 """
249
250
250 checkunfinished(repo, commit=True)
251 checkunfinished(repo, commit=True)
251 wctx = repo[None]
252 wctx = repo[None]
252 merge = len(wctx.parents()) > 1
253 merge = len(wctx.parents()) > 1
253 if merge:
254 if merge:
254 raise error.Abort(_('cannot partially commit a merge '
255 raise error.Abort(_('cannot partially commit a merge '
255 '(use "hg commit" instead)'))
256 '(use "hg commit" instead)'))
256
257
257 def fail(f, msg):
258 def fail(f, msg):
258 raise error.Abort('%s: %s' % (f, msg))
259 raise error.Abort('%s: %s' % (f, msg))
259
260
260 force = opts.get('force')
261 force = opts.get('force')
261 if not force:
262 if not force:
262 vdirs = []
263 vdirs = []
263 match.explicitdir = vdirs.append
264 match.explicitdir = vdirs.append
264 match.bad = fail
265 match.bad = fail
265
266
266 status = repo.status(match=match)
267 status = repo.status(match=match)
267 if not force:
268 if not force:
268 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
269 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
269 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
270 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
270 diffopts.nodates = True
271 diffopts.nodates = True
271 diffopts.git = True
272 diffopts.git = True
272 diffopts.showfunc = True
273 diffopts.showfunc = True
273 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
274 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
274 originalchunks = patch.parsepatch(originaldiff)
275 originalchunks = patch.parsepatch(originaldiff)
275
276
276 # 1. filter patch, since we are intending to apply subset of it
277 # 1. filter patch, since we are intending to apply subset of it
277 try:
278 try:
278 chunks, newopts = filterfn(ui, originalchunks)
279 chunks, newopts = filterfn(ui, originalchunks)
279 except patch.PatchError as err:
280 except patch.PatchError as err:
280 raise error.Abort(_('error parsing patch: %s') % err)
281 raise error.Abort(_('error parsing patch: %s') % err)
281 opts.update(newopts)
282 opts.update(newopts)
282
283
283 # We need to keep a backup of files that have been newly added and
284 # We need to keep a backup of files that have been newly added and
284 # modified during the recording process because there is a previous
285 # modified during the recording process because there is a previous
285 # version without the edit in the workdir
286 # version without the edit in the workdir
286 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
287 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
287 contenders = set()
288 contenders = set()
288 for h in chunks:
289 for h in chunks:
289 try:
290 try:
290 contenders.update(set(h.files()))
291 contenders.update(set(h.files()))
291 except AttributeError:
292 except AttributeError:
292 pass
293 pass
293
294
294 changed = status.modified + status.added + status.removed
295 changed = status.modified + status.added + status.removed
295 newfiles = [f for f in changed if f in contenders]
296 newfiles = [f for f in changed if f in contenders]
296 if not newfiles:
297 if not newfiles:
297 ui.status(_('no changes to record\n'))
298 ui.status(_('no changes to record\n'))
298 return 0
299 return 0
299
300
300 modified = set(status.modified)
301 modified = set(status.modified)
301
302
302 # 2. backup changed files, so we can restore them in the end
303 # 2. backup changed files, so we can restore them in the end
303
304
304 if backupall:
305 if backupall:
305 tobackup = changed
306 tobackup = changed
306 else:
307 else:
307 tobackup = [f for f in newfiles if f in modified or f in \
308 tobackup = [f for f in newfiles if f in modified or f in \
308 newlyaddedandmodifiedfiles]
309 newlyaddedandmodifiedfiles]
309 backups = {}
310 backups = {}
310 if tobackup:
311 if tobackup:
311 backupdir = repo.vfs.join('record-backups')
312 backupdir = repo.vfs.join('record-backups')
312 try:
313 try:
313 os.mkdir(backupdir)
314 os.mkdir(backupdir)
314 except OSError as err:
315 except OSError as err:
315 if err.errno != errno.EEXIST:
316 if err.errno != errno.EEXIST:
316 raise
317 raise
317 try:
318 try:
318 # backup continues
319 # backup continues
319 for f in tobackup:
320 for f in tobackup:
320 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
321 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
321 dir=backupdir)
322 dir=backupdir)
322 os.close(fd)
323 os.close(fd)
323 ui.debug('backup %r as %r\n' % (f, tmpname))
324 ui.debug('backup %r as %r\n' % (f, tmpname))
324 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
325 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
325 backups[f] = tmpname
326 backups[f] = tmpname
326
327
327 fp = stringio()
328 fp = stringio()
328 for c in chunks:
329 for c in chunks:
329 fname = c.filename()
330 fname = c.filename()
330 if fname in backups:
331 if fname in backups:
331 c.write(fp)
332 c.write(fp)
332 dopatch = fp.tell()
333 dopatch = fp.tell()
333 fp.seek(0)
334 fp.seek(0)
334
335
335 # 2.5 optionally review / modify patch in text editor
336 # 2.5 optionally review / modify patch in text editor
336 if opts.get('review', False):
337 if opts.get('review', False):
337 patchtext = (crecordmod.diffhelptext
338 patchtext = (crecordmod.diffhelptext
338 + crecordmod.patchhelptext
339 + crecordmod.patchhelptext
339 + fp.read())
340 + fp.read())
340 reviewedpatch = ui.edit(patchtext, "",
341 reviewedpatch = ui.edit(patchtext, "",
341 extra={"suffix": ".diff"},
342 extra={"suffix": ".diff"},
342 repopath=repo.path)
343 repopath=repo.path)
343 fp.truncate(0)
344 fp.truncate(0)
344 fp.write(reviewedpatch)
345 fp.write(reviewedpatch)
345 fp.seek(0)
346 fp.seek(0)
346
347
347 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
348 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
348 # 3a. apply filtered patch to clean repo (clean)
349 # 3a. apply filtered patch to clean repo (clean)
349 if backups:
350 if backups:
350 # Equivalent to hg.revert
351 # Equivalent to hg.revert
351 m = scmutil.matchfiles(repo, backups.keys())
352 m = scmutil.matchfiles(repo, backups.keys())
352 mergemod.update(repo, repo.dirstate.p1(),
353 mergemod.update(repo, repo.dirstate.p1(),
353 False, True, matcher=m)
354 False, True, matcher=m)
354
355
355 # 3b. (apply)
356 # 3b. (apply)
356 if dopatch:
357 if dopatch:
357 try:
358 try:
358 ui.debug('applying patch\n')
359 ui.debug('applying patch\n')
359 ui.debug(fp.getvalue())
360 ui.debug(fp.getvalue())
360 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
361 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
361 except patch.PatchError as err:
362 except patch.PatchError as err:
362 raise error.Abort(str(err))
363 raise error.Abort(str(err))
363 del fp
364 del fp
364
365
365 # 4. We prepared working directory according to filtered
366 # 4. We prepared working directory according to filtered
366 # patch. Now is the time to delegate the job to
367 # patch. Now is the time to delegate the job to
367 # commit/qrefresh or the like!
368 # commit/qrefresh or the like!
368
369
369 # Make all of the pathnames absolute.
370 # Make all of the pathnames absolute.
370 newfiles = [repo.wjoin(nf) for nf in newfiles]
371 newfiles = [repo.wjoin(nf) for nf in newfiles]
371 return commitfunc(ui, repo, *newfiles, **opts)
372 return commitfunc(ui, repo, *newfiles, **opts)
372 finally:
373 finally:
373 # 5. finally restore backed-up files
374 # 5. finally restore backed-up files
374 try:
375 try:
375 dirstate = repo.dirstate
376 dirstate = repo.dirstate
376 for realname, tmpname in backups.iteritems():
377 for realname, tmpname in backups.iteritems():
377 ui.debug('restoring %r to %r\n' % (tmpname, realname))
378 ui.debug('restoring %r to %r\n' % (tmpname, realname))
378
379
379 if dirstate[realname] == 'n':
380 if dirstate[realname] == 'n':
380 # without normallookup, restoring timestamp
381 # without normallookup, restoring timestamp
381 # may cause partially committed files
382 # may cause partially committed files
382 # to be treated as unmodified
383 # to be treated as unmodified
383 dirstate.normallookup(realname)
384 dirstate.normallookup(realname)
384
385
385 # copystat=True here and above are a hack to trick any
386 # copystat=True here and above are a hack to trick any
386 # editors that have f open that we haven't modified them.
387 # editors that have f open that we haven't modified them.
387 #
388 #
388 # Also note that this racy as an editor could notice the
389 # Also note that this racy as an editor could notice the
389 # file's mtime before we've finished writing it.
390 # file's mtime before we've finished writing it.
390 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
391 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
391 os.unlink(tmpname)
392 os.unlink(tmpname)
392 if tobackup:
393 if tobackup:
393 os.rmdir(backupdir)
394 os.rmdir(backupdir)
394 except OSError:
395 except OSError:
395 pass
396 pass
396
397
397 def recordinwlock(ui, repo, message, match, opts):
398 def recordinwlock(ui, repo, message, match, opts):
398 with repo.wlock():
399 with repo.wlock():
399 return recordfunc(ui, repo, message, match, opts)
400 return recordfunc(ui, repo, message, match, opts)
400
401
401 return commit(ui, repo, recordinwlock, pats, opts)
402 return commit(ui, repo, recordinwlock, pats, opts)
402
403
403 def tersestatus(root, statlist, status, ignorefn, ignore):
404 def tersestatus(root, statlist, status, ignorefn, ignore):
404 """
405 """
405 Returns a list of statuses with directory collapsed if all the files in the
406 Returns a list of statuses with directory collapsed if all the files in the
406 directory has the same status.
407 directory has the same status.
407 """
408 """
408
409
409 def numfiles(dirname):
410 def numfiles(dirname):
410 """
411 """
411 Calculates the number of tracked files in a given directory which also
412 Calculates the number of tracked files in a given directory which also
412 includes files which were removed or deleted. Considers ignored files
413 includes files which were removed or deleted. Considers ignored files
413 if ignore argument is True or 'i' is present in status argument.
414 if ignore argument is True or 'i' is present in status argument.
414 """
415 """
415 if lencache.get(dirname):
416 if lencache.get(dirname):
416 return lencache[dirname]
417 return lencache[dirname]
417 if 'i' in status or ignore:
418 if 'i' in status or ignore:
418 def match(localpath):
419 def match(localpath):
419 absolutepath = os.path.join(root, localpath)
420 absolutepath = os.path.join(root, localpath)
420 if os.path.isdir(absolutepath) and isemptydir(absolutepath):
421 if os.path.isdir(absolutepath) and isemptydir(absolutepath):
421 return True
422 return True
422 return False
423 return False
423 else:
424 else:
424 def match(localpath):
425 def match(localpath):
425 # there can be directory whose all the files are ignored and
426 # there can be directory whose all the files are ignored and
426 # hence the drectory should also be ignored while counting
427 # hence the drectory should also be ignored while counting
427 # number of files or subdirs in it's parent directory. This
428 # number of files or subdirs in it's parent directory. This
428 # checks the same.
429 # checks the same.
429 # XXX: We need a better logic here.
430 # XXX: We need a better logic here.
430 if os.path.isdir(os.path.join(root, localpath)):
431 if os.path.isdir(os.path.join(root, localpath)):
431 return isignoreddir(localpath)
432 return isignoreddir(localpath)
432 else:
433 else:
433 # XXX: there can be files which have the ignored pattern but
434 # XXX: there can be files which have the ignored pattern but
434 # are not ignored. That leads to bug in counting number of
435 # are not ignored. That leads to bug in counting number of
435 # tracked files in the directory.
436 # tracked files in the directory.
436 return ignorefn(localpath)
437 return ignorefn(localpath)
437 lendir = 0
438 lendir = 0
438 abspath = os.path.join(root, dirname)
439 abspath = os.path.join(root, dirname)
439 # There might be cases when a directory does not exists as the whole
440 # There might be cases when a directory does not exists as the whole
440 # directory can be removed and/or deleted.
441 # directory can be removed and/or deleted.
441 try:
442 try:
442 for f in os.listdir(abspath):
443 for f in os.listdir(abspath):
443 localpath = os.path.join(dirname, f)
444 localpath = os.path.join(dirname, f)
444 if not match(localpath):
445 if not match(localpath):
445 lendir += 1
446 lendir += 1
446 except OSError:
447 except OSError:
447 pass
448 pass
448 lendir += len(absentdir.get(dirname, []))
449 lendir += len(absentdir.get(dirname, []))
449 lencache[dirname] = lendir
450 lencache[dirname] = lendir
450 return lendir
451 return lendir
451
452
452 def isemptydir(abspath):
453 def isemptydir(abspath):
453 """
454 """
454 Check whether a directory is empty or not, i.e. there is no files in the
455 Check whether a directory is empty or not, i.e. there is no files in the
455 directory and all its subdirectories.
456 directory and all its subdirectories.
456 """
457 """
457 for f in os.listdir(abspath):
458 for f in os.listdir(abspath):
458 fullpath = os.path.join(abspath, f)
459 fullpath = os.path.join(abspath, f)
459 if os.path.isdir(fullpath):
460 if os.path.isdir(fullpath):
460 # recursion here
461 # recursion here
461 ret = isemptydir(fullpath)
462 ret = isemptydir(fullpath)
462 if not ret:
463 if not ret:
463 return False
464 return False
464 else:
465 else:
465 return False
466 return False
466 return True
467 return True
467
468
468 def isignoreddir(localpath):
469 def isignoreddir(localpath):
469 """
470 """
470 This function checks whether the directory contains only ignored files
471 This function checks whether the directory contains only ignored files
471 and hence should the directory be considered ignored. Returns True, if
472 and hence should the directory be considered ignored. Returns True, if
472 that should be ignored otherwise False.
473 that should be ignored otherwise False.
473 """
474 """
474 dirpath = os.path.join(root, localpath)
475 dirpath = os.path.join(root, localpath)
475 for f in os.listdir(dirpath):
476 for f in os.listdir(dirpath):
476 filepath = os.path.join(dirpath, f)
477 filepath = os.path.join(dirpath, f)
477 if os.path.isdir(filepath):
478 if os.path.isdir(filepath):
478 # recursion here
479 # recursion here
479 ret = isignoreddir(os.path.join(localpath, f))
480 ret = isignoreddir(os.path.join(localpath, f))
480 if not ret:
481 if not ret:
481 return False
482 return False
482 else:
483 else:
483 if not ignorefn(os.path.join(localpath, f)):
484 if not ignorefn(os.path.join(localpath, f)):
484 return False
485 return False
485 return True
486 return True
486
487
487 def absentones(removedfiles, missingfiles):
488 def absentones(removedfiles, missingfiles):
488 """
489 """
489 Returns a dictionary of directories with files in it which are either
490 Returns a dictionary of directories with files in it which are either
490 removed or missing (deleted) in them.
491 removed or missing (deleted) in them.
491 """
492 """
492 absentdir = {}
493 absentdir = {}
493 absentfiles = removedfiles + missingfiles
494 absentfiles = removedfiles + missingfiles
494 while absentfiles:
495 while absentfiles:
495 f = absentfiles.pop()
496 f = absentfiles.pop()
496 par = os.path.dirname(f)
497 par = os.path.dirname(f)
497 if par == '':
498 if par == '':
498 continue
499 continue
499 # we need to store files rather than number of files as some files
500 # we need to store files rather than number of files as some files
500 # or subdirectories in a directory can be counted twice. This is
501 # or subdirectories in a directory can be counted twice. This is
501 # also we have used sets here.
502 # also we have used sets here.
502 try:
503 try:
503 absentdir[par].add(f)
504 absentdir[par].add(f)
504 except KeyError:
505 except KeyError:
505 absentdir[par] = set([f])
506 absentdir[par] = set([f])
506 absentfiles.append(par)
507 absentfiles.append(par)
507 return absentdir
508 return absentdir
508
509
509 indexes = {'m': 0, 'a': 1, 'r': 2, 'd': 3, 'u': 4, 'i': 5, 'c': 6}
510 indexes = {'m': 0, 'a': 1, 'r': 2, 'd': 3, 'u': 4, 'i': 5, 'c': 6}
510 # get a dictonary of directories and files which are missing as os.listdir()
511 # get a dictonary of directories and files which are missing as os.listdir()
511 # won't be able to list them.
512 # won't be able to list them.
512 absentdir = absentones(statlist[2], statlist[3])
513 absentdir = absentones(statlist[2], statlist[3])
513 finalrs = [[]] * len(indexes)
514 finalrs = [[]] * len(indexes)
514 didsomethingchanged = False
515 didsomethingchanged = False
515 # dictionary to store number of files and subdir in a directory so that we
516 # dictionary to store number of files and subdir in a directory so that we
516 # don't compute that again.
517 # don't compute that again.
517 lencache = {}
518 lencache = {}
518
519
519 for st in pycompat.bytestr(status):
520 for st in pycompat.bytestr(status):
520
521
521 try:
522 try:
522 ind = indexes[st]
523 ind = indexes[st]
523 except KeyError:
524 except KeyError:
524 # TODO: Need a better error message here
525 # TODO: Need a better error message here
525 raise error.Abort("'%s' not recognized" % st)
526 raise error.Abort("'%s' not recognized" % st)
526
527
527 sfiles = statlist[ind]
528 sfiles = statlist[ind]
528 if not sfiles:
529 if not sfiles:
529 continue
530 continue
530 pardict = {}
531 pardict = {}
531 for a in sfiles:
532 for a in sfiles:
532 par = os.path.dirname(a)
533 par = os.path.dirname(a)
533 pardict.setdefault(par, []).append(a)
534 pardict.setdefault(par, []).append(a)
534
535
535 rs = []
536 rs = []
536 newls = []
537 newls = []
537 for par, files in pardict.iteritems():
538 for par, files in pardict.iteritems():
538 lenpar = numfiles(par)
539 lenpar = numfiles(par)
539 if lenpar == len(files):
540 if lenpar == len(files):
540 newls.append(par)
541 newls.append(par)
541
542
542 if not newls:
543 if not newls:
543 continue
544 continue
544
545
545 while newls:
546 while newls:
546 newel = newls.pop()
547 newel = newls.pop()
547 if newel == '':
548 if newel == '':
548 continue
549 continue
549 parn = os.path.dirname(newel)
550 parn = os.path.dirname(newel)
550 pardict[newel] = []
551 pardict[newel] = []
551 # Adding pycompat.ossep as newel is a directory.
552 # Adding pycompat.ossep as newel is a directory.
552 pardict.setdefault(parn, []).append(newel + pycompat.ossep)
553 pardict.setdefault(parn, []).append(newel + pycompat.ossep)
553 lenpar = numfiles(parn)
554 lenpar = numfiles(parn)
554 if lenpar == len(pardict[parn]):
555 if lenpar == len(pardict[parn]):
555 newls.append(parn)
556 newls.append(parn)
556
557
557 # dict.values() for Py3 compatibility
558 # dict.values() for Py3 compatibility
558 for files in pardict.values():
559 for files in pardict.values():
559 rs.extend(files)
560 rs.extend(files)
560
561
561 rs.sort()
562 rs.sort()
562 finalrs[ind] = rs
563 finalrs[ind] = rs
563 didsomethingchanged = True
564 didsomethingchanged = True
564
565
565 # If nothing is changed, make sure the order of files is preserved.
566 # If nothing is changed, make sure the order of files is preserved.
566 if not didsomethingchanged:
567 if not didsomethingchanged:
567 return statlist
568 return statlist
568
569
569 for x in xrange(len(indexes)):
570 for x in xrange(len(indexes)):
570 if not finalrs[x]:
571 if not finalrs[x]:
571 finalrs[x] = statlist[x]
572 finalrs[x] = statlist[x]
572
573
573 return finalrs
574 return finalrs
574
575
575 def findpossible(cmd, table, strict=False):
576 def findpossible(cmd, table, strict=False):
576 """
577 """
577 Return cmd -> (aliases, command table entry)
578 Return cmd -> (aliases, command table entry)
578 for each matching command.
579 for each matching command.
579 Return debug commands (or their aliases) only if no normal command matches.
580 Return debug commands (or their aliases) only if no normal command matches.
580 """
581 """
581 choice = {}
582 choice = {}
582 debugchoice = {}
583 debugchoice = {}
583
584
584 if cmd in table:
585 if cmd in table:
585 # short-circuit exact matches, "log" alias beats "^log|history"
586 # short-circuit exact matches, "log" alias beats "^log|history"
586 keys = [cmd]
587 keys = [cmd]
587 else:
588 else:
588 keys = table.keys()
589 keys = table.keys()
589
590
590 allcmds = []
591 allcmds = []
591 for e in keys:
592 for e in keys:
592 aliases = parsealiases(e)
593 aliases = parsealiases(e)
593 allcmds.extend(aliases)
594 allcmds.extend(aliases)
594 found = None
595 found = None
595 if cmd in aliases:
596 if cmd in aliases:
596 found = cmd
597 found = cmd
597 elif not strict:
598 elif not strict:
598 for a in aliases:
599 for a in aliases:
599 if a.startswith(cmd):
600 if a.startswith(cmd):
600 found = a
601 found = a
601 break
602 break
602 if found is not None:
603 if found is not None:
603 if aliases[0].startswith("debug") or found.startswith("debug"):
604 if aliases[0].startswith("debug") or found.startswith("debug"):
604 debugchoice[found] = (aliases, table[e])
605 debugchoice[found] = (aliases, table[e])
605 else:
606 else:
606 choice[found] = (aliases, table[e])
607 choice[found] = (aliases, table[e])
607
608
608 if not choice and debugchoice:
609 if not choice and debugchoice:
609 choice = debugchoice
610 choice = debugchoice
610
611
611 return choice, allcmds
612 return choice, allcmds
612
613
613 def findcmd(cmd, table, strict=True):
614 def findcmd(cmd, table, strict=True):
614 """Return (aliases, command table entry) for command string."""
615 """Return (aliases, command table entry) for command string."""
615 choice, allcmds = findpossible(cmd, table, strict)
616 choice, allcmds = findpossible(cmd, table, strict)
616
617
617 if cmd in choice:
618 if cmd in choice:
618 return choice[cmd]
619 return choice[cmd]
619
620
620 if len(choice) > 1:
621 if len(choice) > 1:
621 clist = sorted(choice)
622 clist = sorted(choice)
622 raise error.AmbiguousCommand(cmd, clist)
623 raise error.AmbiguousCommand(cmd, clist)
623
624
624 if choice:
625 if choice:
625 return list(choice.values())[0]
626 return list(choice.values())[0]
626
627
627 raise error.UnknownCommand(cmd, allcmds)
628 raise error.UnknownCommand(cmd, allcmds)
628
629
629 def findrepo(p):
630 def findrepo(p):
630 while not os.path.isdir(os.path.join(p, ".hg")):
631 while not os.path.isdir(os.path.join(p, ".hg")):
631 oldp, p = p, os.path.dirname(p)
632 oldp, p = p, os.path.dirname(p)
632 if p == oldp:
633 if p == oldp:
633 return None
634 return None
634
635
635 return p
636 return p
636
637
637 def bailifchanged(repo, merge=True, hint=None):
638 def bailifchanged(repo, merge=True, hint=None):
638 """ enforce the precondition that working directory must be clean.
639 """ enforce the precondition that working directory must be clean.
639
640
640 'merge' can be set to false if a pending uncommitted merge should be
641 'merge' can be set to false if a pending uncommitted merge should be
641 ignored (such as when 'update --check' runs).
642 ignored (such as when 'update --check' runs).
642
643
643 'hint' is the usual hint given to Abort exception.
644 'hint' is the usual hint given to Abort exception.
644 """
645 """
645
646
646 if merge and repo.dirstate.p2() != nullid:
647 if merge and repo.dirstate.p2() != nullid:
647 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
648 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
648 modified, added, removed, deleted = repo.status()[:4]
649 modified, added, removed, deleted = repo.status()[:4]
649 if modified or added or removed or deleted:
650 if modified or added or removed or deleted:
650 raise error.Abort(_('uncommitted changes'), hint=hint)
651 raise error.Abort(_('uncommitted changes'), hint=hint)
651 ctx = repo[None]
652 ctx = repo[None]
652 for s in sorted(ctx.substate):
653 for s in sorted(ctx.substate):
653 ctx.sub(s).bailifchanged(hint=hint)
654 ctx.sub(s).bailifchanged(hint=hint)
654
655
655 def logmessage(ui, opts):
656 def logmessage(ui, opts):
656 """ get the log message according to -m and -l option """
657 """ get the log message according to -m and -l option """
657 message = opts.get('message')
658 message = opts.get('message')
658 logfile = opts.get('logfile')
659 logfile = opts.get('logfile')
659
660
660 if message and logfile:
661 if message and logfile:
661 raise error.Abort(_('options --message and --logfile are mutually '
662 raise error.Abort(_('options --message and --logfile are mutually '
662 'exclusive'))
663 'exclusive'))
663 if not message and logfile:
664 if not message and logfile:
664 try:
665 try:
665 if isstdiofilename(logfile):
666 if isstdiofilename(logfile):
666 message = ui.fin.read()
667 message = ui.fin.read()
667 else:
668 else:
668 message = '\n'.join(util.readfile(logfile).splitlines())
669 message = '\n'.join(util.readfile(logfile).splitlines())
669 except IOError as inst:
670 except IOError as inst:
670 raise error.Abort(_("can't read commit message '%s': %s") %
671 raise error.Abort(_("can't read commit message '%s': %s") %
671 (logfile, inst.strerror))
672 (logfile, inst.strerror))
672 return message
673 return message
673
674
674 def mergeeditform(ctxorbool, baseformname):
675 def mergeeditform(ctxorbool, baseformname):
675 """return appropriate editform name (referencing a committemplate)
676 """return appropriate editform name (referencing a committemplate)
676
677
677 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
678 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
678 merging is committed.
679 merging is committed.
679
680
680 This returns baseformname with '.merge' appended if it is a merge,
681 This returns baseformname with '.merge' appended if it is a merge,
681 otherwise '.normal' is appended.
682 otherwise '.normal' is appended.
682 """
683 """
683 if isinstance(ctxorbool, bool):
684 if isinstance(ctxorbool, bool):
684 if ctxorbool:
685 if ctxorbool:
685 return baseformname + ".merge"
686 return baseformname + ".merge"
686 elif 1 < len(ctxorbool.parents()):
687 elif 1 < len(ctxorbool.parents()):
687 return baseformname + ".merge"
688 return baseformname + ".merge"
688
689
689 return baseformname + ".normal"
690 return baseformname + ".normal"
690
691
691 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
692 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
692 editform='', **opts):
693 editform='', **opts):
693 """get appropriate commit message editor according to '--edit' option
694 """get appropriate commit message editor according to '--edit' option
694
695
695 'finishdesc' is a function to be called with edited commit message
696 'finishdesc' is a function to be called with edited commit message
696 (= 'description' of the new changeset) just after editing, but
697 (= 'description' of the new changeset) just after editing, but
697 before checking empty-ness. It should return actual text to be
698 before checking empty-ness. It should return actual text to be
698 stored into history. This allows to change description before
699 stored into history. This allows to change description before
699 storing.
700 storing.
700
701
701 'extramsg' is a extra message to be shown in the editor instead of
702 'extramsg' is a extra message to be shown in the editor instead of
702 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
703 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
703 is automatically added.
704 is automatically added.
704
705
705 'editform' is a dot-separated list of names, to distinguish
706 'editform' is a dot-separated list of names, to distinguish
706 the purpose of commit text editing.
707 the purpose of commit text editing.
707
708
708 'getcommiteditor' returns 'commitforceeditor' regardless of
709 'getcommiteditor' returns 'commitforceeditor' regardless of
709 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
710 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
710 they are specific for usage in MQ.
711 they are specific for usage in MQ.
711 """
712 """
712 if edit or finishdesc or extramsg:
713 if edit or finishdesc or extramsg:
713 return lambda r, c, s: commitforceeditor(r, c, s,
714 return lambda r, c, s: commitforceeditor(r, c, s,
714 finishdesc=finishdesc,
715 finishdesc=finishdesc,
715 extramsg=extramsg,
716 extramsg=extramsg,
716 editform=editform)
717 editform=editform)
717 elif editform:
718 elif editform:
718 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
719 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
719 else:
720 else:
720 return commiteditor
721 return commiteditor
721
722
722 def loglimit(opts):
723 def loglimit(opts):
723 """get the log limit according to option -l/--limit"""
724 """get the log limit according to option -l/--limit"""
724 limit = opts.get('limit')
725 limit = opts.get('limit')
725 if limit:
726 if limit:
726 try:
727 try:
727 limit = int(limit)
728 limit = int(limit)
728 except ValueError:
729 except ValueError:
729 raise error.Abort(_('limit must be a positive integer'))
730 raise error.Abort(_('limit must be a positive integer'))
730 if limit <= 0:
731 if limit <= 0:
731 raise error.Abort(_('limit must be positive'))
732 raise error.Abort(_('limit must be positive'))
732 else:
733 else:
733 limit = None
734 limit = None
734 return limit
735 return limit
735
736
736 def makefilename(repo, pat, node, desc=None,
737 def makefilename(repo, pat, node, desc=None,
737 total=None, seqno=None, revwidth=None, pathname=None):
738 total=None, seqno=None, revwidth=None, pathname=None):
738 node_expander = {
739 node_expander = {
739 'H': lambda: hex(node),
740 'H': lambda: hex(node),
740 'R': lambda: str(repo.changelog.rev(node)),
741 'R': lambda: str(repo.changelog.rev(node)),
741 'h': lambda: short(node),
742 'h': lambda: short(node),
742 'm': lambda: re.sub('[^\w]', '_', str(desc))
743 'm': lambda: re.sub('[^\w]', '_', str(desc))
743 }
744 }
744 expander = {
745 expander = {
745 '%': lambda: '%',
746 '%': lambda: '%',
746 'b': lambda: os.path.basename(repo.root),
747 'b': lambda: os.path.basename(repo.root),
747 }
748 }
748
749
749 try:
750 try:
750 if node:
751 if node:
751 expander.update(node_expander)
752 expander.update(node_expander)
752 if node:
753 if node:
753 expander['r'] = (lambda:
754 expander['r'] = (lambda:
754 str(repo.changelog.rev(node)).zfill(revwidth or 0))
755 str(repo.changelog.rev(node)).zfill(revwidth or 0))
755 if total is not None:
756 if total is not None:
756 expander['N'] = lambda: str(total)
757 expander['N'] = lambda: str(total)
757 if seqno is not None:
758 if seqno is not None:
758 expander['n'] = lambda: str(seqno)
759 expander['n'] = lambda: str(seqno)
759 if total is not None and seqno is not None:
760 if total is not None and seqno is not None:
760 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
761 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
761 if pathname is not None:
762 if pathname is not None:
762 expander['s'] = lambda: os.path.basename(pathname)
763 expander['s'] = lambda: os.path.basename(pathname)
763 expander['d'] = lambda: os.path.dirname(pathname) or '.'
764 expander['d'] = lambda: os.path.dirname(pathname) or '.'
764 expander['p'] = lambda: pathname
765 expander['p'] = lambda: pathname
765
766
766 newname = []
767 newname = []
767 patlen = len(pat)
768 patlen = len(pat)
768 i = 0
769 i = 0
769 while i < patlen:
770 while i < patlen:
770 c = pat[i:i + 1]
771 c = pat[i:i + 1]
771 if c == '%':
772 if c == '%':
772 i += 1
773 i += 1
773 c = pat[i:i + 1]
774 c = pat[i:i + 1]
774 c = expander[c]()
775 c = expander[c]()
775 newname.append(c)
776 newname.append(c)
776 i += 1
777 i += 1
777 return ''.join(newname)
778 return ''.join(newname)
778 except KeyError as inst:
779 except KeyError as inst:
779 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
780 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
780 inst.args[0])
781 inst.args[0])
781
782
782 def isstdiofilename(pat):
783 def isstdiofilename(pat):
783 """True if the given pat looks like a filename denoting stdin/stdout"""
784 """True if the given pat looks like a filename denoting stdin/stdout"""
784 return not pat or pat == '-'
785 return not pat or pat == '-'
785
786
786 class _unclosablefile(object):
787 class _unclosablefile(object):
787 def __init__(self, fp):
788 def __init__(self, fp):
788 self._fp = fp
789 self._fp = fp
789
790
790 def close(self):
791 def close(self):
791 pass
792 pass
792
793
793 def __iter__(self):
794 def __iter__(self):
794 return iter(self._fp)
795 return iter(self._fp)
795
796
796 def __getattr__(self, attr):
797 def __getattr__(self, attr):
797 return getattr(self._fp, attr)
798 return getattr(self._fp, attr)
798
799
799 def __enter__(self):
800 def __enter__(self):
800 return self
801 return self
801
802
802 def __exit__(self, exc_type, exc_value, exc_tb):
803 def __exit__(self, exc_type, exc_value, exc_tb):
803 pass
804 pass
804
805
805 def makefileobj(repo, pat, node=None, desc=None, total=None,
806 def makefileobj(repo, pat, node=None, desc=None, total=None,
806 seqno=None, revwidth=None, mode='wb', modemap=None,
807 seqno=None, revwidth=None, mode='wb', modemap=None,
807 pathname=None):
808 pathname=None):
808
809
809 writable = mode not in ('r', 'rb')
810 writable = mode not in ('r', 'rb')
810
811
811 if isstdiofilename(pat):
812 if isstdiofilename(pat):
812 if writable:
813 if writable:
813 fp = repo.ui.fout
814 fp = repo.ui.fout
814 else:
815 else:
815 fp = repo.ui.fin
816 fp = repo.ui.fin
816 return _unclosablefile(fp)
817 return _unclosablefile(fp)
817 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
818 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
818 if modemap is not None:
819 if modemap is not None:
819 mode = modemap.get(fn, mode)
820 mode = modemap.get(fn, mode)
820 if mode == 'wb':
821 if mode == 'wb':
821 modemap[fn] = 'ab'
822 modemap[fn] = 'ab'
822 return open(fn, mode)
823 return open(fn, mode)
823
824
824 def openrevlog(repo, cmd, file_, opts):
825 def openrevlog(repo, cmd, file_, opts):
825 """opens the changelog, manifest, a filelog or a given revlog"""
826 """opens the changelog, manifest, a filelog or a given revlog"""
826 cl = opts['changelog']
827 cl = opts['changelog']
827 mf = opts['manifest']
828 mf = opts['manifest']
828 dir = opts['dir']
829 dir = opts['dir']
829 msg = None
830 msg = None
830 if cl and mf:
831 if cl and mf:
831 msg = _('cannot specify --changelog and --manifest at the same time')
832 msg = _('cannot specify --changelog and --manifest at the same time')
832 elif cl and dir:
833 elif cl and dir:
833 msg = _('cannot specify --changelog and --dir at the same time')
834 msg = _('cannot specify --changelog and --dir at the same time')
834 elif cl or mf or dir:
835 elif cl or mf or dir:
835 if file_:
836 if file_:
836 msg = _('cannot specify filename with --changelog or --manifest')
837 msg = _('cannot specify filename with --changelog or --manifest')
837 elif not repo:
838 elif not repo:
838 msg = _('cannot specify --changelog or --manifest or --dir '
839 msg = _('cannot specify --changelog or --manifest or --dir '
839 'without a repository')
840 'without a repository')
840 if msg:
841 if msg:
841 raise error.Abort(msg)
842 raise error.Abort(msg)
842
843
843 r = None
844 r = None
844 if repo:
845 if repo:
845 if cl:
846 if cl:
846 r = repo.unfiltered().changelog
847 r = repo.unfiltered().changelog
847 elif dir:
848 elif dir:
848 if 'treemanifest' not in repo.requirements:
849 if 'treemanifest' not in repo.requirements:
849 raise error.Abort(_("--dir can only be used on repos with "
850 raise error.Abort(_("--dir can only be used on repos with "
850 "treemanifest enabled"))
851 "treemanifest enabled"))
851 dirlog = repo.manifestlog._revlog.dirlog(dir)
852 dirlog = repo.manifestlog._revlog.dirlog(dir)
852 if len(dirlog):
853 if len(dirlog):
853 r = dirlog
854 r = dirlog
854 elif mf:
855 elif mf:
855 r = repo.manifestlog._revlog
856 r = repo.manifestlog._revlog
856 elif file_:
857 elif file_:
857 filelog = repo.file(file_)
858 filelog = repo.file(file_)
858 if len(filelog):
859 if len(filelog):
859 r = filelog
860 r = filelog
860 if not r:
861 if not r:
861 if not file_:
862 if not file_:
862 raise error.CommandError(cmd, _('invalid arguments'))
863 raise error.CommandError(cmd, _('invalid arguments'))
863 if not os.path.isfile(file_):
864 if not os.path.isfile(file_):
864 raise error.Abort(_("revlog '%s' not found") % file_)
865 raise error.Abort(_("revlog '%s' not found") % file_)
865 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
866 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
866 file_[:-2] + ".i")
867 file_[:-2] + ".i")
867 return r
868 return r
868
869
869 def copy(ui, repo, pats, opts, rename=False):
870 def copy(ui, repo, pats, opts, rename=False):
870 # called with the repo lock held
871 # called with the repo lock held
871 #
872 #
872 # hgsep => pathname that uses "/" to separate directories
873 # hgsep => pathname that uses "/" to separate directories
873 # ossep => pathname that uses os.sep to separate directories
874 # ossep => pathname that uses os.sep to separate directories
874 cwd = repo.getcwd()
875 cwd = repo.getcwd()
875 targets = {}
876 targets = {}
876 after = opts.get("after")
877 after = opts.get("after")
877 dryrun = opts.get("dry_run")
878 dryrun = opts.get("dry_run")
878 wctx = repo[None]
879 wctx = repo[None]
879
880
880 def walkpat(pat):
881 def walkpat(pat):
881 srcs = []
882 srcs = []
882 if after:
883 if after:
883 badstates = '?'
884 badstates = '?'
884 else:
885 else:
885 badstates = '?r'
886 badstates = '?r'
886 m = scmutil.match(wctx, [pat], opts, globbed=True)
887 m = scmutil.match(wctx, [pat], opts, globbed=True)
887 for abs in wctx.walk(m):
888 for abs in wctx.walk(m):
888 state = repo.dirstate[abs]
889 state = repo.dirstate[abs]
889 rel = m.rel(abs)
890 rel = m.rel(abs)
890 exact = m.exact(abs)
891 exact = m.exact(abs)
891 if state in badstates:
892 if state in badstates:
892 if exact and state == '?':
893 if exact and state == '?':
893 ui.warn(_('%s: not copying - file is not managed\n') % rel)
894 ui.warn(_('%s: not copying - file is not managed\n') % rel)
894 if exact and state == 'r':
895 if exact and state == 'r':
895 ui.warn(_('%s: not copying - file has been marked for'
896 ui.warn(_('%s: not copying - file has been marked for'
896 ' remove\n') % rel)
897 ' remove\n') % rel)
897 continue
898 continue
898 # abs: hgsep
899 # abs: hgsep
899 # rel: ossep
900 # rel: ossep
900 srcs.append((abs, rel, exact))
901 srcs.append((abs, rel, exact))
901 return srcs
902 return srcs
902
903
903 # abssrc: hgsep
904 # abssrc: hgsep
904 # relsrc: ossep
905 # relsrc: ossep
905 # otarget: ossep
906 # otarget: ossep
906 def copyfile(abssrc, relsrc, otarget, exact):
907 def copyfile(abssrc, relsrc, otarget, exact):
907 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
908 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
908 if '/' in abstarget:
909 if '/' in abstarget:
909 # We cannot normalize abstarget itself, this would prevent
910 # We cannot normalize abstarget itself, this would prevent
910 # case only renames, like a => A.
911 # case only renames, like a => A.
911 abspath, absname = abstarget.rsplit('/', 1)
912 abspath, absname = abstarget.rsplit('/', 1)
912 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
913 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
913 reltarget = repo.pathto(abstarget, cwd)
914 reltarget = repo.pathto(abstarget, cwd)
914 target = repo.wjoin(abstarget)
915 target = repo.wjoin(abstarget)
915 src = repo.wjoin(abssrc)
916 src = repo.wjoin(abssrc)
916 state = repo.dirstate[abstarget]
917 state = repo.dirstate[abstarget]
917
918
918 scmutil.checkportable(ui, abstarget)
919 scmutil.checkportable(ui, abstarget)
919
920
920 # check for collisions
921 # check for collisions
921 prevsrc = targets.get(abstarget)
922 prevsrc = targets.get(abstarget)
922 if prevsrc is not None:
923 if prevsrc is not None:
923 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
924 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
924 (reltarget, repo.pathto(abssrc, cwd),
925 (reltarget, repo.pathto(abssrc, cwd),
925 repo.pathto(prevsrc, cwd)))
926 repo.pathto(prevsrc, cwd)))
926 return
927 return
927
928
928 # check for overwrites
929 # check for overwrites
929 exists = os.path.lexists(target)
930 exists = os.path.lexists(target)
930 samefile = False
931 samefile = False
931 if exists and abssrc != abstarget:
932 if exists and abssrc != abstarget:
932 if (repo.dirstate.normalize(abssrc) ==
933 if (repo.dirstate.normalize(abssrc) ==
933 repo.dirstate.normalize(abstarget)):
934 repo.dirstate.normalize(abstarget)):
934 if not rename:
935 if not rename:
935 ui.warn(_("%s: can't copy - same file\n") % reltarget)
936 ui.warn(_("%s: can't copy - same file\n") % reltarget)
936 return
937 return
937 exists = False
938 exists = False
938 samefile = True
939 samefile = True
939
940
940 if not after and exists or after and state in 'mn':
941 if not after and exists or after and state in 'mn':
941 if not opts['force']:
942 if not opts['force']:
942 if state in 'mn':
943 if state in 'mn':
943 msg = _('%s: not overwriting - file already committed\n')
944 msg = _('%s: not overwriting - file already committed\n')
944 if after:
945 if after:
945 flags = '--after --force'
946 flags = '--after --force'
946 else:
947 else:
947 flags = '--force'
948 flags = '--force'
948 if rename:
949 if rename:
949 hint = _('(hg rename %s to replace the file by '
950 hint = _('(hg rename %s to replace the file by '
950 'recording a rename)\n') % flags
951 'recording a rename)\n') % flags
951 else:
952 else:
952 hint = _('(hg copy %s to replace the file by '
953 hint = _('(hg copy %s to replace the file by '
953 'recording a copy)\n') % flags
954 'recording a copy)\n') % flags
954 else:
955 else:
955 msg = _('%s: not overwriting - file exists\n')
956 msg = _('%s: not overwriting - file exists\n')
956 if rename:
957 if rename:
957 hint = _('(hg rename --after to record the rename)\n')
958 hint = _('(hg rename --after to record the rename)\n')
958 else:
959 else:
959 hint = _('(hg copy --after to record the copy)\n')
960 hint = _('(hg copy --after to record the copy)\n')
960 ui.warn(msg % reltarget)
961 ui.warn(msg % reltarget)
961 ui.warn(hint)
962 ui.warn(hint)
962 return
963 return
963
964
964 if after:
965 if after:
965 if not exists:
966 if not exists:
966 if rename:
967 if rename:
967 ui.warn(_('%s: not recording move - %s does not exist\n') %
968 ui.warn(_('%s: not recording move - %s does not exist\n') %
968 (relsrc, reltarget))
969 (relsrc, reltarget))
969 else:
970 else:
970 ui.warn(_('%s: not recording copy - %s does not exist\n') %
971 ui.warn(_('%s: not recording copy - %s does not exist\n') %
971 (relsrc, reltarget))
972 (relsrc, reltarget))
972 return
973 return
973 elif not dryrun:
974 elif not dryrun:
974 try:
975 try:
975 if exists:
976 if exists:
976 os.unlink(target)
977 os.unlink(target)
977 targetdir = os.path.dirname(target) or '.'
978 targetdir = os.path.dirname(target) or '.'
978 if not os.path.isdir(targetdir):
979 if not os.path.isdir(targetdir):
979 os.makedirs(targetdir)
980 os.makedirs(targetdir)
980 if samefile:
981 if samefile:
981 tmp = target + "~hgrename"
982 tmp = target + "~hgrename"
982 os.rename(src, tmp)
983 os.rename(src, tmp)
983 os.rename(tmp, target)
984 os.rename(tmp, target)
984 else:
985 else:
985 util.copyfile(src, target)
986 util.copyfile(src, target)
986 srcexists = True
987 srcexists = True
987 except IOError as inst:
988 except IOError as inst:
988 if inst.errno == errno.ENOENT:
989 if inst.errno == errno.ENOENT:
989 ui.warn(_('%s: deleted in working directory\n') % relsrc)
990 ui.warn(_('%s: deleted in working directory\n') % relsrc)
990 srcexists = False
991 srcexists = False
991 else:
992 else:
992 ui.warn(_('%s: cannot copy - %s\n') %
993 ui.warn(_('%s: cannot copy - %s\n') %
993 (relsrc, inst.strerror))
994 (relsrc, inst.strerror))
994 return True # report a failure
995 return True # report a failure
995
996
996 if ui.verbose or not exact:
997 if ui.verbose or not exact:
997 if rename:
998 if rename:
998 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
999 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
999 else:
1000 else:
1000 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1001 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1001
1002
1002 targets[abstarget] = abssrc
1003 targets[abstarget] = abssrc
1003
1004
1004 # fix up dirstate
1005 # fix up dirstate
1005 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1006 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1006 dryrun=dryrun, cwd=cwd)
1007 dryrun=dryrun, cwd=cwd)
1007 if rename and not dryrun:
1008 if rename and not dryrun:
1008 if not after and srcexists and not samefile:
1009 if not after and srcexists and not samefile:
1009 repo.wvfs.unlinkpath(abssrc)
1010 repo.wvfs.unlinkpath(abssrc)
1010 wctx.forget([abssrc])
1011 wctx.forget([abssrc])
1011
1012
1012 # pat: ossep
1013 # pat: ossep
1013 # dest ossep
1014 # dest ossep
1014 # srcs: list of (hgsep, hgsep, ossep, bool)
1015 # srcs: list of (hgsep, hgsep, ossep, bool)
1015 # return: function that takes hgsep and returns ossep
1016 # return: function that takes hgsep and returns ossep
1016 def targetpathfn(pat, dest, srcs):
1017 def targetpathfn(pat, dest, srcs):
1017 if os.path.isdir(pat):
1018 if os.path.isdir(pat):
1018 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1019 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1019 abspfx = util.localpath(abspfx)
1020 abspfx = util.localpath(abspfx)
1020 if destdirexists:
1021 if destdirexists:
1021 striplen = len(os.path.split(abspfx)[0])
1022 striplen = len(os.path.split(abspfx)[0])
1022 else:
1023 else:
1023 striplen = len(abspfx)
1024 striplen = len(abspfx)
1024 if striplen:
1025 if striplen:
1025 striplen += len(pycompat.ossep)
1026 striplen += len(pycompat.ossep)
1026 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1027 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1027 elif destdirexists:
1028 elif destdirexists:
1028 res = lambda p: os.path.join(dest,
1029 res = lambda p: os.path.join(dest,
1029 os.path.basename(util.localpath(p)))
1030 os.path.basename(util.localpath(p)))
1030 else:
1031 else:
1031 res = lambda p: dest
1032 res = lambda p: dest
1032 return res
1033 return res
1033
1034
1034 # pat: ossep
1035 # pat: ossep
1035 # dest ossep
1036 # dest ossep
1036 # srcs: list of (hgsep, hgsep, ossep, bool)
1037 # srcs: list of (hgsep, hgsep, ossep, bool)
1037 # return: function that takes hgsep and returns ossep
1038 # return: function that takes hgsep and returns ossep
1038 def targetpathafterfn(pat, dest, srcs):
1039 def targetpathafterfn(pat, dest, srcs):
1039 if matchmod.patkind(pat):
1040 if matchmod.patkind(pat):
1040 # a mercurial pattern
1041 # a mercurial pattern
1041 res = lambda p: os.path.join(dest,
1042 res = lambda p: os.path.join(dest,
1042 os.path.basename(util.localpath(p)))
1043 os.path.basename(util.localpath(p)))
1043 else:
1044 else:
1044 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1045 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1045 if len(abspfx) < len(srcs[0][0]):
1046 if len(abspfx) < len(srcs[0][0]):
1046 # A directory. Either the target path contains the last
1047 # A directory. Either the target path contains the last
1047 # component of the source path or it does not.
1048 # component of the source path or it does not.
1048 def evalpath(striplen):
1049 def evalpath(striplen):
1049 score = 0
1050 score = 0
1050 for s in srcs:
1051 for s in srcs:
1051 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1052 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1052 if os.path.lexists(t):
1053 if os.path.lexists(t):
1053 score += 1
1054 score += 1
1054 return score
1055 return score
1055
1056
1056 abspfx = util.localpath(abspfx)
1057 abspfx = util.localpath(abspfx)
1057 striplen = len(abspfx)
1058 striplen = len(abspfx)
1058 if striplen:
1059 if striplen:
1059 striplen += len(pycompat.ossep)
1060 striplen += len(pycompat.ossep)
1060 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1061 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1061 score = evalpath(striplen)
1062 score = evalpath(striplen)
1062 striplen1 = len(os.path.split(abspfx)[0])
1063 striplen1 = len(os.path.split(abspfx)[0])
1063 if striplen1:
1064 if striplen1:
1064 striplen1 += len(pycompat.ossep)
1065 striplen1 += len(pycompat.ossep)
1065 if evalpath(striplen1) > score:
1066 if evalpath(striplen1) > score:
1066 striplen = striplen1
1067 striplen = striplen1
1067 res = lambda p: os.path.join(dest,
1068 res = lambda p: os.path.join(dest,
1068 util.localpath(p)[striplen:])
1069 util.localpath(p)[striplen:])
1069 else:
1070 else:
1070 # a file
1071 # a file
1071 if destdirexists:
1072 if destdirexists:
1072 res = lambda p: os.path.join(dest,
1073 res = lambda p: os.path.join(dest,
1073 os.path.basename(util.localpath(p)))
1074 os.path.basename(util.localpath(p)))
1074 else:
1075 else:
1075 res = lambda p: dest
1076 res = lambda p: dest
1076 return res
1077 return res
1077
1078
1078 pats = scmutil.expandpats(pats)
1079 pats = scmutil.expandpats(pats)
1079 if not pats:
1080 if not pats:
1080 raise error.Abort(_('no source or destination specified'))
1081 raise error.Abort(_('no source or destination specified'))
1081 if len(pats) == 1:
1082 if len(pats) == 1:
1082 raise error.Abort(_('no destination specified'))
1083 raise error.Abort(_('no destination specified'))
1083 dest = pats.pop()
1084 dest = pats.pop()
1084 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1085 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1085 if not destdirexists:
1086 if not destdirexists:
1086 if len(pats) > 1 or matchmod.patkind(pats[0]):
1087 if len(pats) > 1 or matchmod.patkind(pats[0]):
1087 raise error.Abort(_('with multiple sources, destination must be an '
1088 raise error.Abort(_('with multiple sources, destination must be an '
1088 'existing directory'))
1089 'existing directory'))
1089 if util.endswithsep(dest):
1090 if util.endswithsep(dest):
1090 raise error.Abort(_('destination %s is not a directory') % dest)
1091 raise error.Abort(_('destination %s is not a directory') % dest)
1091
1092
1092 tfn = targetpathfn
1093 tfn = targetpathfn
1093 if after:
1094 if after:
1094 tfn = targetpathafterfn
1095 tfn = targetpathafterfn
1095 copylist = []
1096 copylist = []
1096 for pat in pats:
1097 for pat in pats:
1097 srcs = walkpat(pat)
1098 srcs = walkpat(pat)
1098 if not srcs:
1099 if not srcs:
1099 continue
1100 continue
1100 copylist.append((tfn(pat, dest, srcs), srcs))
1101 copylist.append((tfn(pat, dest, srcs), srcs))
1101 if not copylist:
1102 if not copylist:
1102 raise error.Abort(_('no files to copy'))
1103 raise error.Abort(_('no files to copy'))
1103
1104
1104 errors = 0
1105 errors = 0
1105 for targetpath, srcs in copylist:
1106 for targetpath, srcs in copylist:
1106 for abssrc, relsrc, exact in srcs:
1107 for abssrc, relsrc, exact in srcs:
1107 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1108 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1108 errors += 1
1109 errors += 1
1109
1110
1110 if errors:
1111 if errors:
1111 ui.warn(_('(consider using --after)\n'))
1112 ui.warn(_('(consider using --after)\n'))
1112
1113
1113 return errors != 0
1114 return errors != 0
1114
1115
1115 ## facility to let extension process additional data into an import patch
1116 ## facility to let extension process additional data into an import patch
1116 # list of identifier to be executed in order
1117 # list of identifier to be executed in order
1117 extrapreimport = [] # run before commit
1118 extrapreimport = [] # run before commit
1118 extrapostimport = [] # run after commit
1119 extrapostimport = [] # run after commit
1119 # mapping from identifier to actual import function
1120 # mapping from identifier to actual import function
1120 #
1121 #
1121 # 'preimport' are run before the commit is made and are provided the following
1122 # 'preimport' are run before the commit is made and are provided the following
1122 # arguments:
1123 # arguments:
1123 # - repo: the localrepository instance,
1124 # - repo: the localrepository instance,
1124 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1125 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1125 # - extra: the future extra dictionary of the changeset, please mutate it,
1126 # - extra: the future extra dictionary of the changeset, please mutate it,
1126 # - opts: the import options.
1127 # - opts: the import options.
1127 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1128 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1128 # mutation of in memory commit and more. Feel free to rework the code to get
1129 # mutation of in memory commit and more. Feel free to rework the code to get
1129 # there.
1130 # there.
1130 extrapreimportmap = {}
1131 extrapreimportmap = {}
1131 # 'postimport' are run after the commit is made and are provided the following
1132 # 'postimport' are run after the commit is made and are provided the following
1132 # argument:
1133 # argument:
1133 # - ctx: the changectx created by import.
1134 # - ctx: the changectx created by import.
1134 extrapostimportmap = {}
1135 extrapostimportmap = {}
1135
1136
1136 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
1137 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
1137 """Utility function used by commands.import to import a single patch
1138 """Utility function used by commands.import to import a single patch
1138
1139
1139 This function is explicitly defined here to help the evolve extension to
1140 This function is explicitly defined here to help the evolve extension to
1140 wrap this part of the import logic.
1141 wrap this part of the import logic.
1141
1142
1142 The API is currently a bit ugly because it a simple code translation from
1143 The API is currently a bit ugly because it a simple code translation from
1143 the import command. Feel free to make it better.
1144 the import command. Feel free to make it better.
1144
1145
1145 :hunk: a patch (as a binary string)
1146 :hunk: a patch (as a binary string)
1146 :parents: nodes that will be parent of the created commit
1147 :parents: nodes that will be parent of the created commit
1147 :opts: the full dict of option passed to the import command
1148 :opts: the full dict of option passed to the import command
1148 :msgs: list to save commit message to.
1149 :msgs: list to save commit message to.
1149 (used in case we need to save it when failing)
1150 (used in case we need to save it when failing)
1150 :updatefunc: a function that update a repo to a given node
1151 :updatefunc: a function that update a repo to a given node
1151 updatefunc(<repo>, <node>)
1152 updatefunc(<repo>, <node>)
1152 """
1153 """
1153 # avoid cycle context -> subrepo -> cmdutil
1154 # avoid cycle context -> subrepo -> cmdutil
1154 from . import context
1155 from . import context
1155 extractdata = patch.extract(ui, hunk)
1156 extractdata = patch.extract(ui, hunk)
1156 tmpname = extractdata.get('filename')
1157 tmpname = extractdata.get('filename')
1157 message = extractdata.get('message')
1158 message = extractdata.get('message')
1158 user = opts.get('user') or extractdata.get('user')
1159 user = opts.get('user') or extractdata.get('user')
1159 date = opts.get('date') or extractdata.get('date')
1160 date = opts.get('date') or extractdata.get('date')
1160 branch = extractdata.get('branch')
1161 branch = extractdata.get('branch')
1161 nodeid = extractdata.get('nodeid')
1162 nodeid = extractdata.get('nodeid')
1162 p1 = extractdata.get('p1')
1163 p1 = extractdata.get('p1')
1163 p2 = extractdata.get('p2')
1164 p2 = extractdata.get('p2')
1164
1165
1165 nocommit = opts.get('no_commit')
1166 nocommit = opts.get('no_commit')
1166 importbranch = opts.get('import_branch')
1167 importbranch = opts.get('import_branch')
1167 update = not opts.get('bypass')
1168 update = not opts.get('bypass')
1168 strip = opts["strip"]
1169 strip = opts["strip"]
1169 prefix = opts["prefix"]
1170 prefix = opts["prefix"]
1170 sim = float(opts.get('similarity') or 0)
1171 sim = float(opts.get('similarity') or 0)
1171 if not tmpname:
1172 if not tmpname:
1172 return (None, None, False)
1173 return (None, None, False)
1173
1174
1174 rejects = False
1175 rejects = False
1175
1176
1176 try:
1177 try:
1177 cmdline_message = logmessage(ui, opts)
1178 cmdline_message = logmessage(ui, opts)
1178 if cmdline_message:
1179 if cmdline_message:
1179 # pickup the cmdline msg
1180 # pickup the cmdline msg
1180 message = cmdline_message
1181 message = cmdline_message
1181 elif message:
1182 elif message:
1182 # pickup the patch msg
1183 # pickup the patch msg
1183 message = message.strip()
1184 message = message.strip()
1184 else:
1185 else:
1185 # launch the editor
1186 # launch the editor
1186 message = None
1187 message = None
1187 ui.debug('message:\n%s\n' % message)
1188 ui.debug('message:\n%s\n' % message)
1188
1189
1189 if len(parents) == 1:
1190 if len(parents) == 1:
1190 parents.append(repo[nullid])
1191 parents.append(repo[nullid])
1191 if opts.get('exact'):
1192 if opts.get('exact'):
1192 if not nodeid or not p1:
1193 if not nodeid or not p1:
1193 raise error.Abort(_('not a Mercurial patch'))
1194 raise error.Abort(_('not a Mercurial patch'))
1194 p1 = repo[p1]
1195 p1 = repo[p1]
1195 p2 = repo[p2 or nullid]
1196 p2 = repo[p2 or nullid]
1196 elif p2:
1197 elif p2:
1197 try:
1198 try:
1198 p1 = repo[p1]
1199 p1 = repo[p1]
1199 p2 = repo[p2]
1200 p2 = repo[p2]
1200 # Without any options, consider p2 only if the
1201 # Without any options, consider p2 only if the
1201 # patch is being applied on top of the recorded
1202 # patch is being applied on top of the recorded
1202 # first parent.
1203 # first parent.
1203 if p1 != parents[0]:
1204 if p1 != parents[0]:
1204 p1 = parents[0]
1205 p1 = parents[0]
1205 p2 = repo[nullid]
1206 p2 = repo[nullid]
1206 except error.RepoError:
1207 except error.RepoError:
1207 p1, p2 = parents
1208 p1, p2 = parents
1208 if p2.node() == nullid:
1209 if p2.node() == nullid:
1209 ui.warn(_("warning: import the patch as a normal revision\n"
1210 ui.warn(_("warning: import the patch as a normal revision\n"
1210 "(use --exact to import the patch as a merge)\n"))
1211 "(use --exact to import the patch as a merge)\n"))
1211 else:
1212 else:
1212 p1, p2 = parents
1213 p1, p2 = parents
1213
1214
1214 n = None
1215 n = None
1215 if update:
1216 if update:
1216 if p1 != parents[0]:
1217 if p1 != parents[0]:
1217 updatefunc(repo, p1.node())
1218 updatefunc(repo, p1.node())
1218 if p2 != parents[1]:
1219 if p2 != parents[1]:
1219 repo.setparents(p1.node(), p2.node())
1220 repo.setparents(p1.node(), p2.node())
1220
1221
1221 if opts.get('exact') or importbranch:
1222 if opts.get('exact') or importbranch:
1222 repo.dirstate.setbranch(branch or 'default')
1223 repo.dirstate.setbranch(branch or 'default')
1223
1224
1224 partial = opts.get('partial', False)
1225 partial = opts.get('partial', False)
1225 files = set()
1226 files = set()
1226 try:
1227 try:
1227 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1228 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1228 files=files, eolmode=None, similarity=sim / 100.0)
1229 files=files, eolmode=None, similarity=sim / 100.0)
1229 except patch.PatchError as e:
1230 except patch.PatchError as e:
1230 if not partial:
1231 if not partial:
1231 raise error.Abort(str(e))
1232 raise error.Abort(str(e))
1232 if partial:
1233 if partial:
1233 rejects = True
1234 rejects = True
1234
1235
1235 files = list(files)
1236 files = list(files)
1236 if nocommit:
1237 if nocommit:
1237 if message:
1238 if message:
1238 msgs.append(message)
1239 msgs.append(message)
1239 else:
1240 else:
1240 if opts.get('exact') or p2:
1241 if opts.get('exact') or p2:
1241 # If you got here, you either use --force and know what
1242 # If you got here, you either use --force and know what
1242 # you are doing or used --exact or a merge patch while
1243 # you are doing or used --exact or a merge patch while
1243 # being updated to its first parent.
1244 # being updated to its first parent.
1244 m = None
1245 m = None
1245 else:
1246 else:
1246 m = scmutil.matchfiles(repo, files or [])
1247 m = scmutil.matchfiles(repo, files or [])
1247 editform = mergeeditform(repo[None], 'import.normal')
1248 editform = mergeeditform(repo[None], 'import.normal')
1248 if opts.get('exact'):
1249 if opts.get('exact'):
1249 editor = None
1250 editor = None
1250 else:
1251 else:
1251 editor = getcommiteditor(editform=editform, **opts)
1252 editor = getcommiteditor(editform=editform, **opts)
1252 extra = {}
1253 extra = {}
1253 for idfunc in extrapreimport:
1254 for idfunc in extrapreimport:
1254 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1255 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1255 overrides = {}
1256 overrides = {}
1256 if partial:
1257 if partial:
1257 overrides[('ui', 'allowemptycommit')] = True
1258 overrides[('ui', 'allowemptycommit')] = True
1258 with repo.ui.configoverride(overrides, 'import'):
1259 with repo.ui.configoverride(overrides, 'import'):
1259 n = repo.commit(message, user,
1260 n = repo.commit(message, user,
1260 date, match=m,
1261 date, match=m,
1261 editor=editor, extra=extra)
1262 editor=editor, extra=extra)
1262 for idfunc in extrapostimport:
1263 for idfunc in extrapostimport:
1263 extrapostimportmap[idfunc](repo[n])
1264 extrapostimportmap[idfunc](repo[n])
1264 else:
1265 else:
1265 if opts.get('exact') or importbranch:
1266 if opts.get('exact') or importbranch:
1266 branch = branch or 'default'
1267 branch = branch or 'default'
1267 else:
1268 else:
1268 branch = p1.branch()
1269 branch = p1.branch()
1269 store = patch.filestore()
1270 store = patch.filestore()
1270 try:
1271 try:
1271 files = set()
1272 files = set()
1272 try:
1273 try:
1273 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1274 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1274 files, eolmode=None)
1275 files, eolmode=None)
1275 except patch.PatchError as e:
1276 except patch.PatchError as e:
1276 raise error.Abort(str(e))
1277 raise error.Abort(str(e))
1277 if opts.get('exact'):
1278 if opts.get('exact'):
1278 editor = None
1279 editor = None
1279 else:
1280 else:
1280 editor = getcommiteditor(editform='import.bypass')
1281 editor = getcommiteditor(editform='import.bypass')
1281 memctx = context.memctx(repo, (p1.node(), p2.node()),
1282 memctx = context.memctx(repo, (p1.node(), p2.node()),
1282 message,
1283 message,
1283 files=files,
1284 files=files,
1284 filectxfn=store,
1285 filectxfn=store,
1285 user=user,
1286 user=user,
1286 date=date,
1287 date=date,
1287 branch=branch,
1288 branch=branch,
1288 editor=editor)
1289 editor=editor)
1289 n = memctx.commit()
1290 n = memctx.commit()
1290 finally:
1291 finally:
1291 store.close()
1292 store.close()
1292 if opts.get('exact') and nocommit:
1293 if opts.get('exact') and nocommit:
1293 # --exact with --no-commit is still useful in that it does merge
1294 # --exact with --no-commit is still useful in that it does merge
1294 # and branch bits
1295 # and branch bits
1295 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1296 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1296 elif opts.get('exact') and hex(n) != nodeid:
1297 elif opts.get('exact') and hex(n) != nodeid:
1297 raise error.Abort(_('patch is damaged or loses information'))
1298 raise error.Abort(_('patch is damaged or loses information'))
1298 msg = _('applied to working directory')
1299 msg = _('applied to working directory')
1299 if n:
1300 if n:
1300 # i18n: refers to a short changeset id
1301 # i18n: refers to a short changeset id
1301 msg = _('created %s') % short(n)
1302 msg = _('created %s') % short(n)
1302 return (msg, n, rejects)
1303 return (msg, n, rejects)
1303 finally:
1304 finally:
1304 os.unlink(tmpname)
1305 os.unlink(tmpname)
1305
1306
1306 # facility to let extensions include additional data in an exported patch
1307 # facility to let extensions include additional data in an exported patch
1307 # list of identifiers to be executed in order
1308 # list of identifiers to be executed in order
1308 extraexport = []
1309 extraexport = []
1309 # mapping from identifier to actual export function
1310 # mapping from identifier to actual export function
1310 # function as to return a string to be added to the header or None
1311 # function as to return a string to be added to the header or None
1311 # it is given two arguments (sequencenumber, changectx)
1312 # it is given two arguments (sequencenumber, changectx)
1312 extraexportmap = {}
1313 extraexportmap = {}
1313
1314
1314 def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
1315 def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
1315 node = scmutil.binnode(ctx)
1316 node = scmutil.binnode(ctx)
1316 parents = [p.node() for p in ctx.parents() if p]
1317 parents = [p.node() for p in ctx.parents() if p]
1317 branch = ctx.branch()
1318 branch = ctx.branch()
1318 if switch_parent:
1319 if switch_parent:
1319 parents.reverse()
1320 parents.reverse()
1320
1321
1321 if parents:
1322 if parents:
1322 prev = parents[0]
1323 prev = parents[0]
1323 else:
1324 else:
1324 prev = nullid
1325 prev = nullid
1325
1326
1326 write("# HG changeset patch\n")
1327 write("# HG changeset patch\n")
1327 write("# User %s\n" % ctx.user())
1328 write("# User %s\n" % ctx.user())
1328 write("# Date %d %d\n" % ctx.date())
1329 write("# Date %d %d\n" % ctx.date())
1329 write("# %s\n" % util.datestr(ctx.date()))
1330 write("# %s\n" % util.datestr(ctx.date()))
1330 if branch and branch != 'default':
1331 if branch and branch != 'default':
1331 write("# Branch %s\n" % branch)
1332 write("# Branch %s\n" % branch)
1332 write("# Node ID %s\n" % hex(node))
1333 write("# Node ID %s\n" % hex(node))
1333 write("# Parent %s\n" % hex(prev))
1334 write("# Parent %s\n" % hex(prev))
1334 if len(parents) > 1:
1335 if len(parents) > 1:
1335 write("# Parent %s\n" % hex(parents[1]))
1336 write("# Parent %s\n" % hex(parents[1]))
1336
1337
1337 for headerid in extraexport:
1338 for headerid in extraexport:
1338 header = extraexportmap[headerid](seqno, ctx)
1339 header = extraexportmap[headerid](seqno, ctx)
1339 if header is not None:
1340 if header is not None:
1340 write('# %s\n' % header)
1341 write('# %s\n' % header)
1341 write(ctx.description().rstrip())
1342 write(ctx.description().rstrip())
1342 write("\n\n")
1343 write("\n\n")
1343
1344
1344 for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts):
1345 for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts):
1345 write(chunk, label=label)
1346 write(chunk, label=label)
1346
1347
1347 def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
1348 def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
1348 opts=None, match=None):
1349 opts=None, match=None):
1349 '''export changesets as hg patches
1350 '''export changesets as hg patches
1350
1351
1351 Args:
1352 Args:
1352 repo: The repository from which we're exporting revisions.
1353 repo: The repository from which we're exporting revisions.
1353 revs: A list of revisions to export as revision numbers.
1354 revs: A list of revisions to export as revision numbers.
1354 fntemplate: An optional string to use for generating patch file names.
1355 fntemplate: An optional string to use for generating patch file names.
1355 fp: An optional file-like object to which patches should be written.
1356 fp: An optional file-like object to which patches should be written.
1356 switch_parent: If True, show diffs against second parent when not nullid.
1357 switch_parent: If True, show diffs against second parent when not nullid.
1357 Default is false, which always shows diff against p1.
1358 Default is false, which always shows diff against p1.
1358 opts: diff options to use for generating the patch.
1359 opts: diff options to use for generating the patch.
1359 match: If specified, only export changes to files matching this matcher.
1360 match: If specified, only export changes to files matching this matcher.
1360
1361
1361 Returns:
1362 Returns:
1362 Nothing.
1363 Nothing.
1363
1364
1364 Side Effect:
1365 Side Effect:
1365 "HG Changeset Patch" data is emitted to one of the following
1366 "HG Changeset Patch" data is emitted to one of the following
1366 destinations:
1367 destinations:
1367 fp is specified: All revs are written to the specified
1368 fp is specified: All revs are written to the specified
1368 file-like object.
1369 file-like object.
1369 fntemplate specified: Each rev is written to a unique file named using
1370 fntemplate specified: Each rev is written to a unique file named using
1370 the given template.
1371 the given template.
1371 Neither fp nor template specified: All revs written to repo.ui.write()
1372 Neither fp nor template specified: All revs written to repo.ui.write()
1372 '''
1373 '''
1373
1374
1374 total = len(revs)
1375 total = len(revs)
1375 revwidth = max(len(str(rev)) for rev in revs)
1376 revwidth = max(len(str(rev)) for rev in revs)
1376 filemode = {}
1377 filemode = {}
1377
1378
1378 write = None
1379 write = None
1379 dest = '<unnamed>'
1380 dest = '<unnamed>'
1380 if fp:
1381 if fp:
1381 dest = getattr(fp, 'name', dest)
1382 dest = getattr(fp, 'name', dest)
1382 def write(s, **kw):
1383 def write(s, **kw):
1383 fp.write(s)
1384 fp.write(s)
1384 elif not fntemplate:
1385 elif not fntemplate:
1385 write = repo.ui.write
1386 write = repo.ui.write
1386
1387
1387 for seqno, rev in enumerate(revs, 1):
1388 for seqno, rev in enumerate(revs, 1):
1388 ctx = repo[rev]
1389 ctx = repo[rev]
1389 fo = None
1390 fo = None
1390 if not fp and fntemplate:
1391 if not fp and fntemplate:
1391 desc_lines = ctx.description().rstrip().split('\n')
1392 desc_lines = ctx.description().rstrip().split('\n')
1392 desc = desc_lines[0] #Commit always has a first line.
1393 desc = desc_lines[0] #Commit always has a first line.
1393 fo = makefileobj(repo, fntemplate, ctx.node(), desc=desc,
1394 fo = makefileobj(repo, fntemplate, ctx.node(), desc=desc,
1394 total=total, seqno=seqno, revwidth=revwidth,
1395 total=total, seqno=seqno, revwidth=revwidth,
1395 mode='wb', modemap=filemode)
1396 mode='wb', modemap=filemode)
1396 dest = fo.name
1397 dest = fo.name
1397 def write(s, **kw):
1398 def write(s, **kw):
1398 fo.write(s)
1399 fo.write(s)
1399 if not dest.startswith('<'):
1400 if not dest.startswith('<'):
1400 repo.ui.note("%s\n" % dest)
1401 repo.ui.note("%s\n" % dest)
1401 _exportsingle(
1402 _exportsingle(
1402 repo, ctx, match, switch_parent, rev, seqno, write, opts)
1403 repo, ctx, match, switch_parent, rev, seqno, write, opts)
1403 if fo is not None:
1404 if fo is not None:
1404 fo.close()
1405 fo.close()
1405
1406
1406 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1407 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1407 changes=None, stat=False, fp=None, prefix='',
1408 changes=None, stat=False, fp=None, prefix='',
1408 root='', listsubrepos=False):
1409 root='', listsubrepos=False):
1409 '''show diff or diffstat.'''
1410 '''show diff or diffstat.'''
1410 if fp is None:
1411 if fp is None:
1411 write = ui.write
1412 write = ui.write
1412 else:
1413 else:
1413 def write(s, **kw):
1414 def write(s, **kw):
1414 fp.write(s)
1415 fp.write(s)
1415
1416
1416 if root:
1417 if root:
1417 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1418 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1418 else:
1419 else:
1419 relroot = ''
1420 relroot = ''
1420 if relroot != '':
1421 if relroot != '':
1421 # XXX relative roots currently don't work if the root is within a
1422 # XXX relative roots currently don't work if the root is within a
1422 # subrepo
1423 # subrepo
1423 uirelroot = match.uipath(relroot)
1424 uirelroot = match.uipath(relroot)
1424 relroot += '/'
1425 relroot += '/'
1425 for matchroot in match.files():
1426 for matchroot in match.files():
1426 if not matchroot.startswith(relroot):
1427 if not matchroot.startswith(relroot):
1427 ui.warn(_('warning: %s not inside relative root %s\n') % (
1428 ui.warn(_('warning: %s not inside relative root %s\n') % (
1428 match.uipath(matchroot), uirelroot))
1429 match.uipath(matchroot), uirelroot))
1429
1430
1430 if stat:
1431 if stat:
1431 diffopts = diffopts.copy(context=0)
1432 diffopts = diffopts.copy(context=0)
1432 width = 80
1433 width = 80
1433 if not ui.plain():
1434 if not ui.plain():
1434 width = ui.termwidth()
1435 width = ui.termwidth()
1435 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1436 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1436 prefix=prefix, relroot=relroot)
1437 prefix=prefix, relroot=relroot)
1437 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1438 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1438 width=width):
1439 width=width):
1439 write(chunk, label=label)
1440 write(chunk, label=label)
1440 else:
1441 else:
1441 for chunk, label in patch.diffui(repo, node1, node2, match,
1442 for chunk, label in patch.diffui(repo, node1, node2, match,
1442 changes, diffopts, prefix=prefix,
1443 changes, diffopts, prefix=prefix,
1443 relroot=relroot):
1444 relroot=relroot):
1444 write(chunk, label=label)
1445 write(chunk, label=label)
1445
1446
1446 if listsubrepos:
1447 if listsubrepos:
1447 ctx1 = repo[node1]
1448 ctx1 = repo[node1]
1448 ctx2 = repo[node2]
1449 ctx2 = repo[node2]
1449 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1450 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1450 tempnode2 = node2
1451 tempnode2 = node2
1451 try:
1452 try:
1452 if node2 is not None:
1453 if node2 is not None:
1453 tempnode2 = ctx2.substate[subpath][1]
1454 tempnode2 = ctx2.substate[subpath][1]
1454 except KeyError:
1455 except KeyError:
1455 # A subrepo that existed in node1 was deleted between node1 and
1456 # A subrepo that existed in node1 was deleted between node1 and
1456 # node2 (inclusive). Thus, ctx2's substate won't contain that
1457 # node2 (inclusive). Thus, ctx2's substate won't contain that
1457 # subpath. The best we can do is to ignore it.
1458 # subpath. The best we can do is to ignore it.
1458 tempnode2 = None
1459 tempnode2 = None
1459 submatch = matchmod.subdirmatcher(subpath, match)
1460 submatch = matchmod.subdirmatcher(subpath, match)
1460 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1461 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1461 stat=stat, fp=fp, prefix=prefix)
1462 stat=stat, fp=fp, prefix=prefix)
1462
1463
1463 def _changesetlabels(ctx):
1464 def _changesetlabels(ctx):
1464 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1465 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1465 if ctx.obsolete():
1466 if ctx.obsolete():
1466 labels.append('changeset.obsolete')
1467 labels.append('changeset.obsolete')
1467 if ctx.troubled():
1468 if ctx.troubled():
1468 labels.append('changeset.troubled')
1469 labels.append('changeset.troubled')
1469 for trouble in ctx.troubles():
1470 for trouble in ctx.troubles():
1470 labels.append('trouble.%s' % trouble)
1471 labels.append('trouble.%s' % trouble)
1471 return ' '.join(labels)
1472 return ' '.join(labels)
1472
1473
1473 class changeset_printer(object):
1474 class changeset_printer(object):
1474 '''show changeset information when templating not requested.'''
1475 '''show changeset information when templating not requested.'''
1475
1476
1476 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1477 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1477 self.ui = ui
1478 self.ui = ui
1478 self.repo = repo
1479 self.repo = repo
1479 self.buffered = buffered
1480 self.buffered = buffered
1480 self.matchfn = matchfn
1481 self.matchfn = matchfn
1481 self.diffopts = diffopts
1482 self.diffopts = diffopts
1482 self.header = {}
1483 self.header = {}
1483 self.hunk = {}
1484 self.hunk = {}
1484 self.lastheader = None
1485 self.lastheader = None
1485 self.footer = None
1486 self.footer = None
1486
1487
1487 def flush(self, ctx):
1488 def flush(self, ctx):
1488 rev = ctx.rev()
1489 rev = ctx.rev()
1489 if rev in self.header:
1490 if rev in self.header:
1490 h = self.header[rev]
1491 h = self.header[rev]
1491 if h != self.lastheader:
1492 if h != self.lastheader:
1492 self.lastheader = h
1493 self.lastheader = h
1493 self.ui.write(h)
1494 self.ui.write(h)
1494 del self.header[rev]
1495 del self.header[rev]
1495 if rev in self.hunk:
1496 if rev in self.hunk:
1496 self.ui.write(self.hunk[rev])
1497 self.ui.write(self.hunk[rev])
1497 del self.hunk[rev]
1498 del self.hunk[rev]
1498 return 1
1499 return 1
1499 return 0
1500 return 0
1500
1501
1501 def close(self):
1502 def close(self):
1502 if self.footer:
1503 if self.footer:
1503 self.ui.write(self.footer)
1504 self.ui.write(self.footer)
1504
1505
1505 def show(self, ctx, copies=None, matchfn=None, **props):
1506 def show(self, ctx, copies=None, matchfn=None, **props):
1506 props = pycompat.byteskwargs(props)
1507 props = pycompat.byteskwargs(props)
1507 if self.buffered:
1508 if self.buffered:
1508 self.ui.pushbuffer(labeled=True)
1509 self.ui.pushbuffer(labeled=True)
1509 self._show(ctx, copies, matchfn, props)
1510 self._show(ctx, copies, matchfn, props)
1510 self.hunk[ctx.rev()] = self.ui.popbuffer()
1511 self.hunk[ctx.rev()] = self.ui.popbuffer()
1511 else:
1512 else:
1512 self._show(ctx, copies, matchfn, props)
1513 self._show(ctx, copies, matchfn, props)
1513
1514
1514 def _show(self, ctx, copies, matchfn, props):
1515 def _show(self, ctx, copies, matchfn, props):
1515 '''show a single changeset or file revision'''
1516 '''show a single changeset or file revision'''
1516 changenode = ctx.node()
1517 changenode = ctx.node()
1517 rev = ctx.rev()
1518 rev = ctx.rev()
1518 if self.ui.debugflag:
1519 if self.ui.debugflag:
1519 hexfunc = hex
1520 hexfunc = hex
1520 else:
1521 else:
1521 hexfunc = short
1522 hexfunc = short
1522 # as of now, wctx.node() and wctx.rev() return None, but we want to
1523 # as of now, wctx.node() and wctx.rev() return None, but we want to
1523 # show the same values as {node} and {rev} templatekw
1524 # show the same values as {node} and {rev} templatekw
1524 revnode = (scmutil.intrev(ctx), hexfunc(scmutil.binnode(ctx)))
1525 revnode = (scmutil.intrev(ctx), hexfunc(scmutil.binnode(ctx)))
1525
1526
1526 if self.ui.quiet:
1527 if self.ui.quiet:
1527 self.ui.write("%d:%s\n" % revnode, label='log.node')
1528 self.ui.write("%d:%s\n" % revnode, label='log.node')
1528 return
1529 return
1529
1530
1530 date = util.datestr(ctx.date())
1531 date = util.datestr(ctx.date())
1531
1532
1532 # i18n: column positioning for "hg log"
1533 # i18n: column positioning for "hg log"
1533 self.ui.write(_("changeset: %d:%s\n") % revnode,
1534 self.ui.write(_("changeset: %d:%s\n") % revnode,
1534 label=_changesetlabels(ctx))
1535 label=_changesetlabels(ctx))
1535
1536
1536 # branches are shown first before any other names due to backwards
1537 # branches are shown first before any other names due to backwards
1537 # compatibility
1538 # compatibility
1538 branch = ctx.branch()
1539 branch = ctx.branch()
1539 # don't show the default branch name
1540 # don't show the default branch name
1540 if branch != 'default':
1541 if branch != 'default':
1541 # i18n: column positioning for "hg log"
1542 # i18n: column positioning for "hg log"
1542 self.ui.write(_("branch: %s\n") % branch,
1543 self.ui.write(_("branch: %s\n") % branch,
1543 label='log.branch')
1544 label='log.branch')
1544
1545
1545 for nsname, ns in self.repo.names.iteritems():
1546 for nsname, ns in self.repo.names.iteritems():
1546 # branches has special logic already handled above, so here we just
1547 # branches has special logic already handled above, so here we just
1547 # skip it
1548 # skip it
1548 if nsname == 'branches':
1549 if nsname == 'branches':
1549 continue
1550 continue
1550 # we will use the templatename as the color name since those two
1551 # we will use the templatename as the color name since those two
1551 # should be the same
1552 # should be the same
1552 for name in ns.names(self.repo, changenode):
1553 for name in ns.names(self.repo, changenode):
1553 self.ui.write(ns.logfmt % name,
1554 self.ui.write(ns.logfmt % name,
1554 label='log.%s' % ns.colorname)
1555 label='log.%s' % ns.colorname)
1555 if self.ui.debugflag:
1556 if self.ui.debugflag:
1556 # i18n: column positioning for "hg log"
1557 # i18n: column positioning for "hg log"
1557 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1558 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1558 label='log.phase')
1559 label='log.phase')
1559 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1560 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1560 label = 'log.parent changeset.%s' % pctx.phasestr()
1561 label = 'log.parent changeset.%s' % pctx.phasestr()
1561 # i18n: column positioning for "hg log"
1562 # i18n: column positioning for "hg log"
1562 self.ui.write(_("parent: %d:%s\n")
1563 self.ui.write(_("parent: %d:%s\n")
1563 % (pctx.rev(), hexfunc(pctx.node())),
1564 % (pctx.rev(), hexfunc(pctx.node())),
1564 label=label)
1565 label=label)
1565
1566
1566 if self.ui.debugflag and rev is not None:
1567 if self.ui.debugflag and rev is not None:
1567 mnode = ctx.manifestnode()
1568 mnode = ctx.manifestnode()
1568 # i18n: column positioning for "hg log"
1569 # i18n: column positioning for "hg log"
1569 self.ui.write(_("manifest: %d:%s\n") %
1570 self.ui.write(_("manifest: %d:%s\n") %
1570 (self.repo.manifestlog._revlog.rev(mnode),
1571 (self.repo.manifestlog._revlog.rev(mnode),
1571 hex(mnode)),
1572 hex(mnode)),
1572 label='ui.debug log.manifest')
1573 label='ui.debug log.manifest')
1573 # i18n: column positioning for "hg log"
1574 # i18n: column positioning for "hg log"
1574 self.ui.write(_("user: %s\n") % ctx.user(),
1575 self.ui.write(_("user: %s\n") % ctx.user(),
1575 label='log.user')
1576 label='log.user')
1576 # i18n: column positioning for "hg log"
1577 # i18n: column positioning for "hg log"
1577 self.ui.write(_("date: %s\n") % date,
1578 self.ui.write(_("date: %s\n") % date,
1578 label='log.date')
1579 label='log.date')
1579
1580
1580 if ctx.troubled():
1581 if ctx.troubled():
1581 # i18n: column positioning for "hg log"
1582 # i18n: column positioning for "hg log"
1582 self.ui.write(_("trouble: %s\n") % ', '.join(ctx.troubles()),
1583 self.ui.write(_("trouble: %s\n") % ', '.join(ctx.troubles()),
1583 label='log.trouble')
1584 label='log.trouble')
1584
1585
1585 self._exthook(ctx)
1586 self._exthook(ctx)
1586
1587
1587 if self.ui.debugflag:
1588 if self.ui.debugflag:
1588 files = ctx.p1().status(ctx)[:3]
1589 files = ctx.p1().status(ctx)[:3]
1589 for key, value in zip([# i18n: column positioning for "hg log"
1590 for key, value in zip([# i18n: column positioning for "hg log"
1590 _("files:"),
1591 _("files:"),
1591 # i18n: column positioning for "hg log"
1592 # i18n: column positioning for "hg log"
1592 _("files+:"),
1593 _("files+:"),
1593 # i18n: column positioning for "hg log"
1594 # i18n: column positioning for "hg log"
1594 _("files-:")], files):
1595 _("files-:")], files):
1595 if value:
1596 if value:
1596 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1597 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1597 label='ui.debug log.files')
1598 label='ui.debug log.files')
1598 elif ctx.files() and self.ui.verbose:
1599 elif ctx.files() and self.ui.verbose:
1599 # i18n: column positioning for "hg log"
1600 # i18n: column positioning for "hg log"
1600 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1601 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1601 label='ui.note log.files')
1602 label='ui.note log.files')
1602 if copies and self.ui.verbose:
1603 if copies and self.ui.verbose:
1603 copies = ['%s (%s)' % c for c in copies]
1604 copies = ['%s (%s)' % c for c in copies]
1604 # i18n: column positioning for "hg log"
1605 # i18n: column positioning for "hg log"
1605 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1606 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1606 label='ui.note log.copies')
1607 label='ui.note log.copies')
1607
1608
1608 extra = ctx.extra()
1609 extra = ctx.extra()
1609 if extra and self.ui.debugflag:
1610 if extra and self.ui.debugflag:
1610 for key, value in sorted(extra.items()):
1611 for key, value in sorted(extra.items()):
1611 # i18n: column positioning for "hg log"
1612 # i18n: column positioning for "hg log"
1612 self.ui.write(_("extra: %s=%s\n")
1613 self.ui.write(_("extra: %s=%s\n")
1613 % (key, util.escapestr(value)),
1614 % (key, util.escapestr(value)),
1614 label='ui.debug log.extra')
1615 label='ui.debug log.extra')
1615
1616
1616 description = ctx.description().strip()
1617 description = ctx.description().strip()
1617 if description:
1618 if description:
1618 if self.ui.verbose:
1619 if self.ui.verbose:
1619 self.ui.write(_("description:\n"),
1620 self.ui.write(_("description:\n"),
1620 label='ui.note log.description')
1621 label='ui.note log.description')
1621 self.ui.write(description,
1622 self.ui.write(description,
1622 label='ui.note log.description')
1623 label='ui.note log.description')
1623 self.ui.write("\n\n")
1624 self.ui.write("\n\n")
1624 else:
1625 else:
1625 # i18n: column positioning for "hg log"
1626 # i18n: column positioning for "hg log"
1626 self.ui.write(_("summary: %s\n") %
1627 self.ui.write(_("summary: %s\n") %
1627 description.splitlines()[0],
1628 description.splitlines()[0],
1628 label='log.summary')
1629 label='log.summary')
1629 self.ui.write("\n")
1630 self.ui.write("\n")
1630
1631
1631 self.showpatch(ctx, matchfn)
1632 self.showpatch(ctx, matchfn)
1632
1633
1633 def _exthook(self, ctx):
1634 def _exthook(self, ctx):
1634 '''empty method used by extension as a hook point
1635 '''empty method used by extension as a hook point
1635 '''
1636 '''
1636 pass
1637 pass
1637
1638
1638 def showpatch(self, ctx, matchfn):
1639 def showpatch(self, ctx, matchfn):
1639 if not matchfn:
1640 if not matchfn:
1640 matchfn = self.matchfn
1641 matchfn = self.matchfn
1641 if matchfn:
1642 if matchfn:
1642 stat = self.diffopts.get('stat')
1643 stat = self.diffopts.get('stat')
1643 diff = self.diffopts.get('patch')
1644 diff = self.diffopts.get('patch')
1644 diffopts = patch.diffallopts(self.ui, self.diffopts)
1645 diffopts = patch.diffallopts(self.ui, self.diffopts)
1645 node = ctx.node()
1646 node = ctx.node()
1646 prev = ctx.p1().node()
1647 prev = ctx.p1().node()
1647 if stat:
1648 if stat:
1648 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1649 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1649 match=matchfn, stat=True)
1650 match=matchfn, stat=True)
1650 if diff:
1651 if diff:
1651 if stat:
1652 if stat:
1652 self.ui.write("\n")
1653 self.ui.write("\n")
1653 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1654 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1654 match=matchfn, stat=False)
1655 match=matchfn, stat=False)
1655 self.ui.write("\n")
1656 self.ui.write("\n")
1656
1657
1657 class jsonchangeset(changeset_printer):
1658 class jsonchangeset(changeset_printer):
1658 '''format changeset information.'''
1659 '''format changeset information.'''
1659
1660
1660 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1661 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1661 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1662 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1662 self.cache = {}
1663 self.cache = {}
1663 self._first = True
1664 self._first = True
1664
1665
1665 def close(self):
1666 def close(self):
1666 if not self._first:
1667 if not self._first:
1667 self.ui.write("\n]\n")
1668 self.ui.write("\n]\n")
1668 else:
1669 else:
1669 self.ui.write("[]\n")
1670 self.ui.write("[]\n")
1670
1671
1671 def _show(self, ctx, copies, matchfn, props):
1672 def _show(self, ctx, copies, matchfn, props):
1672 '''show a single changeset or file revision'''
1673 '''show a single changeset or file revision'''
1673 rev = ctx.rev()
1674 rev = ctx.rev()
1674 if rev is None:
1675 if rev is None:
1675 jrev = jnode = 'null'
1676 jrev = jnode = 'null'
1676 else:
1677 else:
1677 jrev = '%d' % rev
1678 jrev = '%d' % rev
1678 jnode = '"%s"' % hex(ctx.node())
1679 jnode = '"%s"' % hex(ctx.node())
1679 j = encoding.jsonescape
1680 j = encoding.jsonescape
1680
1681
1681 if self._first:
1682 if self._first:
1682 self.ui.write("[\n {")
1683 self.ui.write("[\n {")
1683 self._first = False
1684 self._first = False
1684 else:
1685 else:
1685 self.ui.write(",\n {")
1686 self.ui.write(",\n {")
1686
1687
1687 if self.ui.quiet:
1688 if self.ui.quiet:
1688 self.ui.write(('\n "rev": %s') % jrev)
1689 self.ui.write(('\n "rev": %s') % jrev)
1689 self.ui.write((',\n "node": %s') % jnode)
1690 self.ui.write((',\n "node": %s') % jnode)
1690 self.ui.write('\n }')
1691 self.ui.write('\n }')
1691 return
1692 return
1692
1693
1693 self.ui.write(('\n "rev": %s') % jrev)
1694 self.ui.write(('\n "rev": %s') % jrev)
1694 self.ui.write((',\n "node": %s') % jnode)
1695 self.ui.write((',\n "node": %s') % jnode)
1695 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1696 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1696 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1697 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1697 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1698 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1698 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1699 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1699 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1700 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1700
1701
1701 self.ui.write((',\n "bookmarks": [%s]') %
1702 self.ui.write((',\n "bookmarks": [%s]') %
1702 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1703 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1703 self.ui.write((',\n "tags": [%s]') %
1704 self.ui.write((',\n "tags": [%s]') %
1704 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1705 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1705 self.ui.write((',\n "parents": [%s]') %
1706 self.ui.write((',\n "parents": [%s]') %
1706 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1707 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1707
1708
1708 if self.ui.debugflag:
1709 if self.ui.debugflag:
1709 if rev is None:
1710 if rev is None:
1710 jmanifestnode = 'null'
1711 jmanifestnode = 'null'
1711 else:
1712 else:
1712 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1713 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1713 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1714 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1714
1715
1715 self.ui.write((',\n "extra": {%s}') %
1716 self.ui.write((',\n "extra": {%s}') %
1716 ", ".join('"%s": "%s"' % (j(k), j(v))
1717 ", ".join('"%s": "%s"' % (j(k), j(v))
1717 for k, v in ctx.extra().items()))
1718 for k, v in ctx.extra().items()))
1718
1719
1719 files = ctx.p1().status(ctx)
1720 files = ctx.p1().status(ctx)
1720 self.ui.write((',\n "modified": [%s]') %
1721 self.ui.write((',\n "modified": [%s]') %
1721 ", ".join('"%s"' % j(f) for f in files[0]))
1722 ", ".join('"%s"' % j(f) for f in files[0]))
1722 self.ui.write((',\n "added": [%s]') %
1723 self.ui.write((',\n "added": [%s]') %
1723 ", ".join('"%s"' % j(f) for f in files[1]))
1724 ", ".join('"%s"' % j(f) for f in files[1]))
1724 self.ui.write((',\n "removed": [%s]') %
1725 self.ui.write((',\n "removed": [%s]') %
1725 ", ".join('"%s"' % j(f) for f in files[2]))
1726 ", ".join('"%s"' % j(f) for f in files[2]))
1726
1727
1727 elif self.ui.verbose:
1728 elif self.ui.verbose:
1728 self.ui.write((',\n "files": [%s]') %
1729 self.ui.write((',\n "files": [%s]') %
1729 ", ".join('"%s"' % j(f) for f in ctx.files()))
1730 ", ".join('"%s"' % j(f) for f in ctx.files()))
1730
1731
1731 if copies:
1732 if copies:
1732 self.ui.write((',\n "copies": {%s}') %
1733 self.ui.write((',\n "copies": {%s}') %
1733 ", ".join('"%s": "%s"' % (j(k), j(v))
1734 ", ".join('"%s": "%s"' % (j(k), j(v))
1734 for k, v in copies))
1735 for k, v in copies))
1735
1736
1736 matchfn = self.matchfn
1737 matchfn = self.matchfn
1737 if matchfn:
1738 if matchfn:
1738 stat = self.diffopts.get('stat')
1739 stat = self.diffopts.get('stat')
1739 diff = self.diffopts.get('patch')
1740 diff = self.diffopts.get('patch')
1740 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1741 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1741 node, prev = ctx.node(), ctx.p1().node()
1742 node, prev = ctx.node(), ctx.p1().node()
1742 if stat:
1743 if stat:
1743 self.ui.pushbuffer()
1744 self.ui.pushbuffer()
1744 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1745 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1745 match=matchfn, stat=True)
1746 match=matchfn, stat=True)
1746 self.ui.write((',\n "diffstat": "%s"')
1747 self.ui.write((',\n "diffstat": "%s"')
1747 % j(self.ui.popbuffer()))
1748 % j(self.ui.popbuffer()))
1748 if diff:
1749 if diff:
1749 self.ui.pushbuffer()
1750 self.ui.pushbuffer()
1750 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1751 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1751 match=matchfn, stat=False)
1752 match=matchfn, stat=False)
1752 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1753 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1753
1754
1754 self.ui.write("\n }")
1755 self.ui.write("\n }")
1755
1756
1756 class changeset_templater(changeset_printer):
1757 class changeset_templater(changeset_printer):
1757 '''format changeset information.'''
1758 '''format changeset information.'''
1758
1759
1759 # Arguments before "buffered" used to be positional. Consider not
1760 # Arguments before "buffered" used to be positional. Consider not
1760 # adding/removing arguments before "buffered" to not break callers.
1761 # adding/removing arguments before "buffered" to not break callers.
1761 def __init__(self, ui, repo, tmplspec, matchfn=None, diffopts=None,
1762 def __init__(self, ui, repo, tmplspec, matchfn=None, diffopts=None,
1762 buffered=False):
1763 buffered=False):
1763 diffopts = diffopts or {}
1764 diffopts = diffopts or {}
1764
1765
1765 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1766 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1766 self.t = formatter.loadtemplater(ui, tmplspec,
1767 self.t = formatter.loadtemplater(ui, tmplspec,
1767 cache=templatekw.defaulttempl)
1768 cache=templatekw.defaulttempl)
1768 self._counter = itertools.count()
1769 self._counter = itertools.count()
1769 self.cache = {}
1770 self.cache = {}
1770
1771
1771 self._tref = tmplspec.ref
1772 self._tref = tmplspec.ref
1772 self._parts = {'header': '', 'footer': '',
1773 self._parts = {'header': '', 'footer': '',
1773 tmplspec.ref: tmplspec.ref,
1774 tmplspec.ref: tmplspec.ref,
1774 'docheader': '', 'docfooter': '',
1775 'docheader': '', 'docfooter': '',
1775 'separator': ''}
1776 'separator': ''}
1776 if tmplspec.mapfile:
1777 if tmplspec.mapfile:
1777 # find correct templates for current mode, for backward
1778 # find correct templates for current mode, for backward
1778 # compatibility with 'log -v/-q/--debug' using a mapfile
1779 # compatibility with 'log -v/-q/--debug' using a mapfile
1779 tmplmodes = [
1780 tmplmodes = [
1780 (True, ''),
1781 (True, ''),
1781 (self.ui.verbose, '_verbose'),
1782 (self.ui.verbose, '_verbose'),
1782 (self.ui.quiet, '_quiet'),
1783 (self.ui.quiet, '_quiet'),
1783 (self.ui.debugflag, '_debug'),
1784 (self.ui.debugflag, '_debug'),
1784 ]
1785 ]
1785 for mode, postfix in tmplmodes:
1786 for mode, postfix in tmplmodes:
1786 for t in self._parts:
1787 for t in self._parts:
1787 cur = t + postfix
1788 cur = t + postfix
1788 if mode and cur in self.t:
1789 if mode and cur in self.t:
1789 self._parts[t] = cur
1790 self._parts[t] = cur
1790 else:
1791 else:
1791 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
1792 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
1792 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
1793 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
1793 self._parts.update(m)
1794 self._parts.update(m)
1794
1795
1795 if self._parts['docheader']:
1796 if self._parts['docheader']:
1796 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1797 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1797
1798
1798 def close(self):
1799 def close(self):
1799 if self._parts['docfooter']:
1800 if self._parts['docfooter']:
1800 if not self.footer:
1801 if not self.footer:
1801 self.footer = ""
1802 self.footer = ""
1802 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1803 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1803 return super(changeset_templater, self).close()
1804 return super(changeset_templater, self).close()
1804
1805
1805 def _show(self, ctx, copies, matchfn, props):
1806 def _show(self, ctx, copies, matchfn, props):
1806 '''show a single changeset or file revision'''
1807 '''show a single changeset or file revision'''
1807 props = props.copy()
1808 props = props.copy()
1808 props.update(templatekw.keywords)
1809 props.update(templatekw.keywords)
1809 props['templ'] = self.t
1810 props['templ'] = self.t
1810 props['ctx'] = ctx
1811 props['ctx'] = ctx
1811 props['repo'] = self.repo
1812 props['repo'] = self.repo
1812 props['ui'] = self.repo.ui
1813 props['ui'] = self.repo.ui
1813 props['index'] = index = next(self._counter)
1814 props['index'] = index = next(self._counter)
1814 props['revcache'] = {'copies': copies}
1815 props['revcache'] = {'copies': copies}
1815 props['cache'] = self.cache
1816 props['cache'] = self.cache
1816 props = pycompat.strkwargs(props)
1817 props = pycompat.strkwargs(props)
1817
1818
1818 # write separator, which wouldn't work well with the header part below
1819 # write separator, which wouldn't work well with the header part below
1819 # since there's inherently a conflict between header (across items) and
1820 # since there's inherently a conflict between header (across items) and
1820 # separator (per item)
1821 # separator (per item)
1821 if self._parts['separator'] and index > 0:
1822 if self._parts['separator'] and index > 0:
1822 self.ui.write(templater.stringify(self.t(self._parts['separator'])))
1823 self.ui.write(templater.stringify(self.t(self._parts['separator'])))
1823
1824
1824 # write header
1825 # write header
1825 if self._parts['header']:
1826 if self._parts['header']:
1826 h = templater.stringify(self.t(self._parts['header'], **props))
1827 h = templater.stringify(self.t(self._parts['header'], **props))
1827 if self.buffered:
1828 if self.buffered:
1828 self.header[ctx.rev()] = h
1829 self.header[ctx.rev()] = h
1829 else:
1830 else:
1830 if self.lastheader != h:
1831 if self.lastheader != h:
1831 self.lastheader = h
1832 self.lastheader = h
1832 self.ui.write(h)
1833 self.ui.write(h)
1833
1834
1834 # write changeset metadata, then patch if requested
1835 # write changeset metadata, then patch if requested
1835 key = self._parts[self._tref]
1836 key = self._parts[self._tref]
1836 self.ui.write(templater.stringify(self.t(key, **props)))
1837 self.ui.write(templater.stringify(self.t(key, **props)))
1837 self.showpatch(ctx, matchfn)
1838 self.showpatch(ctx, matchfn)
1838
1839
1839 if self._parts['footer']:
1840 if self._parts['footer']:
1840 if not self.footer:
1841 if not self.footer:
1841 self.footer = templater.stringify(
1842 self.footer = templater.stringify(
1842 self.t(self._parts['footer'], **props))
1843 self.t(self._parts['footer'], **props))
1843
1844
1844 def logtemplatespec(tmpl, mapfile):
1845 def logtemplatespec(tmpl, mapfile):
1845 if mapfile:
1846 if mapfile:
1846 return formatter.templatespec('changeset', tmpl, mapfile)
1847 return formatter.templatespec('changeset', tmpl, mapfile)
1847 else:
1848 else:
1848 return formatter.templatespec('', tmpl, None)
1849 return formatter.templatespec('', tmpl, None)
1849
1850
1850 def _lookuplogtemplate(ui, tmpl, style):
1851 def _lookuplogtemplate(ui, tmpl, style):
1851 """Find the template matching the given template spec or style
1852 """Find the template matching the given template spec or style
1852
1853
1853 See formatter.lookuptemplate() for details.
1854 See formatter.lookuptemplate() for details.
1854 """
1855 """
1855
1856
1856 # ui settings
1857 # ui settings
1857 if not tmpl and not style: # template are stronger than style
1858 if not tmpl and not style: # template are stronger than style
1858 tmpl = ui.config('ui', 'logtemplate')
1859 tmpl = ui.config('ui', 'logtemplate')
1859 if tmpl:
1860 if tmpl:
1860 return logtemplatespec(templater.unquotestring(tmpl), None)
1861 return logtemplatespec(templater.unquotestring(tmpl), None)
1861 else:
1862 else:
1862 style = util.expandpath(ui.config('ui', 'style'))
1863 style = util.expandpath(ui.config('ui', 'style'))
1863
1864
1864 if not tmpl and style:
1865 if not tmpl and style:
1865 mapfile = style
1866 mapfile = style
1866 if not os.path.split(mapfile)[0]:
1867 if not os.path.split(mapfile)[0]:
1867 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1868 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1868 or templater.templatepath(mapfile))
1869 or templater.templatepath(mapfile))
1869 if mapname:
1870 if mapname:
1870 mapfile = mapname
1871 mapfile = mapname
1871 return logtemplatespec(None, mapfile)
1872 return logtemplatespec(None, mapfile)
1872
1873
1873 if not tmpl:
1874 if not tmpl:
1874 return logtemplatespec(None, None)
1875 return logtemplatespec(None, None)
1875
1876
1876 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1877 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1877
1878
1878 def makelogtemplater(ui, repo, tmpl, buffered=False):
1879 def makelogtemplater(ui, repo, tmpl, buffered=False):
1879 """Create a changeset_templater from a literal template 'tmpl'"""
1880 """Create a changeset_templater from a literal template 'tmpl'"""
1880 spec = logtemplatespec(tmpl, None)
1881 spec = logtemplatespec(tmpl, None)
1881 return changeset_templater(ui, repo, spec, buffered=buffered)
1882 return changeset_templater(ui, repo, spec, buffered=buffered)
1882
1883
1883 def show_changeset(ui, repo, opts, buffered=False):
1884 def show_changeset(ui, repo, opts, buffered=False):
1884 """show one changeset using template or regular display.
1885 """show one changeset using template or regular display.
1885
1886
1886 Display format will be the first non-empty hit of:
1887 Display format will be the first non-empty hit of:
1887 1. option 'template'
1888 1. option 'template'
1888 2. option 'style'
1889 2. option 'style'
1889 3. [ui] setting 'logtemplate'
1890 3. [ui] setting 'logtemplate'
1890 4. [ui] setting 'style'
1891 4. [ui] setting 'style'
1891 If all of these values are either the unset or the empty string,
1892 If all of these values are either the unset or the empty string,
1892 regular display via changeset_printer() is done.
1893 regular display via changeset_printer() is done.
1893 """
1894 """
1894 # options
1895 # options
1895 matchfn = None
1896 matchfn = None
1896 if opts.get('patch') or opts.get('stat'):
1897 if opts.get('patch') or opts.get('stat'):
1897 matchfn = scmutil.matchall(repo)
1898 matchfn = scmutil.matchall(repo)
1898
1899
1899 if opts.get('template') == 'json':
1900 if opts.get('template') == 'json':
1900 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1901 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1901
1902
1902 spec = _lookuplogtemplate(ui, opts.get('template'), opts.get('style'))
1903 spec = _lookuplogtemplate(ui, opts.get('template'), opts.get('style'))
1903
1904
1904 if not spec.ref and not spec.tmpl and not spec.mapfile:
1905 if not spec.ref and not spec.tmpl and not spec.mapfile:
1905 return changeset_printer(ui, repo, matchfn, opts, buffered)
1906 return changeset_printer(ui, repo, matchfn, opts, buffered)
1906
1907
1907 return changeset_templater(ui, repo, spec, matchfn, opts, buffered)
1908 return changeset_templater(ui, repo, spec, matchfn, opts, buffered)
1908
1909
1909 def showmarker(fm, marker, index=None):
1910 def showmarker(fm, marker, index=None):
1910 """utility function to display obsolescence marker in a readable way
1911 """utility function to display obsolescence marker in a readable way
1911
1912
1912 To be used by debug function."""
1913 To be used by debug function."""
1913 if index is not None:
1914 if index is not None:
1914 fm.write('index', '%i ', index)
1915 fm.write('index', '%i ', index)
1915 fm.write('precnode', '%s ', hex(marker.precnode()))
1916 fm.write('precnode', '%s ', hex(marker.precnode()))
1916 succs = marker.succnodes()
1917 succs = marker.succnodes()
1917 fm.condwrite(succs, 'succnodes', '%s ',
1918 fm.condwrite(succs, 'succnodes', '%s ',
1918 fm.formatlist(map(hex, succs), name='node'))
1919 fm.formatlist(map(hex, succs), name='node'))
1919 fm.write('flag', '%X ', marker.flags())
1920 fm.write('flag', '%X ', marker.flags())
1920 parents = marker.parentnodes()
1921 parents = marker.parentnodes()
1921 if parents is not None:
1922 if parents is not None:
1922 fm.write('parentnodes', '{%s} ',
1923 fm.write('parentnodes', '{%s} ',
1923 fm.formatlist(map(hex, parents), name='node', sep=', '))
1924 fm.formatlist(map(hex, parents), name='node', sep=', '))
1924 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1925 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1925 meta = marker.metadata().copy()
1926 meta = marker.metadata().copy()
1926 meta.pop('date', None)
1927 meta.pop('date', None)
1927 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
1928 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
1928 fm.plain('\n')
1929 fm.plain('\n')
1929
1930
1930 def finddate(ui, repo, date):
1931 def finddate(ui, repo, date):
1931 """Find the tipmost changeset that matches the given date spec"""
1932 """Find the tipmost changeset that matches the given date spec"""
1932
1933
1933 df = util.matchdate(date)
1934 df = util.matchdate(date)
1934 m = scmutil.matchall(repo)
1935 m = scmutil.matchall(repo)
1935 results = {}
1936 results = {}
1936
1937
1937 def prep(ctx, fns):
1938 def prep(ctx, fns):
1938 d = ctx.date()
1939 d = ctx.date()
1939 if df(d[0]):
1940 if df(d[0]):
1940 results[ctx.rev()] = d
1941 results[ctx.rev()] = d
1941
1942
1942 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1943 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1943 rev = ctx.rev()
1944 rev = ctx.rev()
1944 if rev in results:
1945 if rev in results:
1945 ui.status(_("found revision %s from %s\n") %
1946 ui.status(_("found revision %s from %s\n") %
1946 (rev, util.datestr(results[rev])))
1947 (rev, util.datestr(results[rev])))
1947 return '%d' % rev
1948 return '%d' % rev
1948
1949
1949 raise error.Abort(_("revision matching date not found"))
1950 raise error.Abort(_("revision matching date not found"))
1950
1951
1951 def increasingwindows(windowsize=8, sizelimit=512):
1952 def increasingwindows(windowsize=8, sizelimit=512):
1952 while True:
1953 while True:
1953 yield windowsize
1954 yield windowsize
1954 if windowsize < sizelimit:
1955 if windowsize < sizelimit:
1955 windowsize *= 2
1956 windowsize *= 2
1956
1957
1957 class FileWalkError(Exception):
1958 class FileWalkError(Exception):
1958 pass
1959 pass
1959
1960
1960 def walkfilerevs(repo, match, follow, revs, fncache):
1961 def walkfilerevs(repo, match, follow, revs, fncache):
1961 '''Walks the file history for the matched files.
1962 '''Walks the file history for the matched files.
1962
1963
1963 Returns the changeset revs that are involved in the file history.
1964 Returns the changeset revs that are involved in the file history.
1964
1965
1965 Throws FileWalkError if the file history can't be walked using
1966 Throws FileWalkError if the file history can't be walked using
1966 filelogs alone.
1967 filelogs alone.
1967 '''
1968 '''
1968 wanted = set()
1969 wanted = set()
1969 copies = []
1970 copies = []
1970 minrev, maxrev = min(revs), max(revs)
1971 minrev, maxrev = min(revs), max(revs)
1971 def filerevgen(filelog, last):
1972 def filerevgen(filelog, last):
1972 """
1973 """
1973 Only files, no patterns. Check the history of each file.
1974 Only files, no patterns. Check the history of each file.
1974
1975
1975 Examines filelog entries within minrev, maxrev linkrev range
1976 Examines filelog entries within minrev, maxrev linkrev range
1976 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1977 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1977 tuples in backwards order
1978 tuples in backwards order
1978 """
1979 """
1979 cl_count = len(repo)
1980 cl_count = len(repo)
1980 revs = []
1981 revs = []
1981 for j in xrange(0, last + 1):
1982 for j in xrange(0, last + 1):
1982 linkrev = filelog.linkrev(j)
1983 linkrev = filelog.linkrev(j)
1983 if linkrev < minrev:
1984 if linkrev < minrev:
1984 continue
1985 continue
1985 # only yield rev for which we have the changelog, it can
1986 # only yield rev for which we have the changelog, it can
1986 # happen while doing "hg log" during a pull or commit
1987 # happen while doing "hg log" during a pull or commit
1987 if linkrev >= cl_count:
1988 if linkrev >= cl_count:
1988 break
1989 break
1989
1990
1990 parentlinkrevs = []
1991 parentlinkrevs = []
1991 for p in filelog.parentrevs(j):
1992 for p in filelog.parentrevs(j):
1992 if p != nullrev:
1993 if p != nullrev:
1993 parentlinkrevs.append(filelog.linkrev(p))
1994 parentlinkrevs.append(filelog.linkrev(p))
1994 n = filelog.node(j)
1995 n = filelog.node(j)
1995 revs.append((linkrev, parentlinkrevs,
1996 revs.append((linkrev, parentlinkrevs,
1996 follow and filelog.renamed(n)))
1997 follow and filelog.renamed(n)))
1997
1998
1998 return reversed(revs)
1999 return reversed(revs)
1999 def iterfiles():
2000 def iterfiles():
2000 pctx = repo['.']
2001 pctx = repo['.']
2001 for filename in match.files():
2002 for filename in match.files():
2002 if follow:
2003 if follow:
2003 if filename not in pctx:
2004 if filename not in pctx:
2004 raise error.Abort(_('cannot follow file not in parent '
2005 raise error.Abort(_('cannot follow file not in parent '
2005 'revision: "%s"') % filename)
2006 'revision: "%s"') % filename)
2006 yield filename, pctx[filename].filenode()
2007 yield filename, pctx[filename].filenode()
2007 else:
2008 else:
2008 yield filename, None
2009 yield filename, None
2009 for filename_node in copies:
2010 for filename_node in copies:
2010 yield filename_node
2011 yield filename_node
2011
2012
2012 for file_, node in iterfiles():
2013 for file_, node in iterfiles():
2013 filelog = repo.file(file_)
2014 filelog = repo.file(file_)
2014 if not len(filelog):
2015 if not len(filelog):
2015 if node is None:
2016 if node is None:
2016 # A zero count may be a directory or deleted file, so
2017 # A zero count may be a directory or deleted file, so
2017 # try to find matching entries on the slow path.
2018 # try to find matching entries on the slow path.
2018 if follow:
2019 if follow:
2019 raise error.Abort(
2020 raise error.Abort(
2020 _('cannot follow nonexistent file: "%s"') % file_)
2021 _('cannot follow nonexistent file: "%s"') % file_)
2021 raise FileWalkError("Cannot walk via filelog")
2022 raise FileWalkError("Cannot walk via filelog")
2022 else:
2023 else:
2023 continue
2024 continue
2024
2025
2025 if node is None:
2026 if node is None:
2026 last = len(filelog) - 1
2027 last = len(filelog) - 1
2027 else:
2028 else:
2028 last = filelog.rev(node)
2029 last = filelog.rev(node)
2029
2030
2030 # keep track of all ancestors of the file
2031 # keep track of all ancestors of the file
2031 ancestors = {filelog.linkrev(last)}
2032 ancestors = {filelog.linkrev(last)}
2032
2033
2033 # iterate from latest to oldest revision
2034 # iterate from latest to oldest revision
2034 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
2035 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
2035 if not follow:
2036 if not follow:
2036 if rev > maxrev:
2037 if rev > maxrev:
2037 continue
2038 continue
2038 else:
2039 else:
2039 # Note that last might not be the first interesting
2040 # Note that last might not be the first interesting
2040 # rev to us:
2041 # rev to us:
2041 # if the file has been changed after maxrev, we'll
2042 # if the file has been changed after maxrev, we'll
2042 # have linkrev(last) > maxrev, and we still need
2043 # have linkrev(last) > maxrev, and we still need
2043 # to explore the file graph
2044 # to explore the file graph
2044 if rev not in ancestors:
2045 if rev not in ancestors:
2045 continue
2046 continue
2046 # XXX insert 1327 fix here
2047 # XXX insert 1327 fix here
2047 if flparentlinkrevs:
2048 if flparentlinkrevs:
2048 ancestors.update(flparentlinkrevs)
2049 ancestors.update(flparentlinkrevs)
2049
2050
2050 fncache.setdefault(rev, []).append(file_)
2051 fncache.setdefault(rev, []).append(file_)
2051 wanted.add(rev)
2052 wanted.add(rev)
2052 if copied:
2053 if copied:
2053 copies.append(copied)
2054 copies.append(copied)
2054
2055
2055 return wanted
2056 return wanted
2056
2057
2057 class _followfilter(object):
2058 class _followfilter(object):
2058 def __init__(self, repo, onlyfirst=False):
2059 def __init__(self, repo, onlyfirst=False):
2059 self.repo = repo
2060 self.repo = repo
2060 self.startrev = nullrev
2061 self.startrev = nullrev
2061 self.roots = set()
2062 self.roots = set()
2062 self.onlyfirst = onlyfirst
2063 self.onlyfirst = onlyfirst
2063
2064
2064 def match(self, rev):
2065 def match(self, rev):
2065 def realparents(rev):
2066 def realparents(rev):
2066 if self.onlyfirst:
2067 if self.onlyfirst:
2067 return self.repo.changelog.parentrevs(rev)[0:1]
2068 return self.repo.changelog.parentrevs(rev)[0:1]
2068 else:
2069 else:
2069 return filter(lambda x: x != nullrev,
2070 return filter(lambda x: x != nullrev,
2070 self.repo.changelog.parentrevs(rev))
2071 self.repo.changelog.parentrevs(rev))
2071
2072
2072 if self.startrev == nullrev:
2073 if self.startrev == nullrev:
2073 self.startrev = rev
2074 self.startrev = rev
2074 return True
2075 return True
2075
2076
2076 if rev > self.startrev:
2077 if rev > self.startrev:
2077 # forward: all descendants
2078 # forward: all descendants
2078 if not self.roots:
2079 if not self.roots:
2079 self.roots.add(self.startrev)
2080 self.roots.add(self.startrev)
2080 for parent in realparents(rev):
2081 for parent in realparents(rev):
2081 if parent in self.roots:
2082 if parent in self.roots:
2082 self.roots.add(rev)
2083 self.roots.add(rev)
2083 return True
2084 return True
2084 else:
2085 else:
2085 # backwards: all parents
2086 # backwards: all parents
2086 if not self.roots:
2087 if not self.roots:
2087 self.roots.update(realparents(self.startrev))
2088 self.roots.update(realparents(self.startrev))
2088 if rev in self.roots:
2089 if rev in self.roots:
2089 self.roots.remove(rev)
2090 self.roots.remove(rev)
2090 self.roots.update(realparents(rev))
2091 self.roots.update(realparents(rev))
2091 return True
2092 return True
2092
2093
2093 return False
2094 return False
2094
2095
2095 def walkchangerevs(repo, match, opts, prepare):
2096 def walkchangerevs(repo, match, opts, prepare):
2096 '''Iterate over files and the revs in which they changed.
2097 '''Iterate over files and the revs in which they changed.
2097
2098
2098 Callers most commonly need to iterate backwards over the history
2099 Callers most commonly need to iterate backwards over the history
2099 in which they are interested. Doing so has awful (quadratic-looking)
2100 in which they are interested. Doing so has awful (quadratic-looking)
2100 performance, so we use iterators in a "windowed" way.
2101 performance, so we use iterators in a "windowed" way.
2101
2102
2102 We walk a window of revisions in the desired order. Within the
2103 We walk a window of revisions in the desired order. Within the
2103 window, we first walk forwards to gather data, then in the desired
2104 window, we first walk forwards to gather data, then in the desired
2104 order (usually backwards) to display it.
2105 order (usually backwards) to display it.
2105
2106
2106 This function returns an iterator yielding contexts. Before
2107 This function returns an iterator yielding contexts. Before
2107 yielding each context, the iterator will first call the prepare
2108 yielding each context, the iterator will first call the prepare
2108 function on each context in the window in forward order.'''
2109 function on each context in the window in forward order.'''
2109
2110
2110 follow = opts.get('follow') or opts.get('follow_first')
2111 follow = opts.get('follow') or opts.get('follow_first')
2111 revs = _logrevs(repo, opts)
2112 revs = _logrevs(repo, opts)
2112 if not revs:
2113 if not revs:
2113 return []
2114 return []
2114 wanted = set()
2115 wanted = set()
2115 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2116 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2116 opts.get('removed'))
2117 opts.get('removed'))
2117 fncache = {}
2118 fncache = {}
2118 change = repo.changectx
2119 change = repo.changectx
2119
2120
2120 # First step is to fill wanted, the set of revisions that we want to yield.
2121 # First step is to fill wanted, the set of revisions that we want to yield.
2121 # When it does not induce extra cost, we also fill fncache for revisions in
2122 # When it does not induce extra cost, we also fill fncache for revisions in
2122 # wanted: a cache of filenames that were changed (ctx.files()) and that
2123 # wanted: a cache of filenames that were changed (ctx.files()) and that
2123 # match the file filtering conditions.
2124 # match the file filtering conditions.
2124
2125
2125 if match.always():
2126 if match.always():
2126 # No files, no patterns. Display all revs.
2127 # No files, no patterns. Display all revs.
2127 wanted = revs
2128 wanted = revs
2128 elif not slowpath:
2129 elif not slowpath:
2129 # We only have to read through the filelog to find wanted revisions
2130 # We only have to read through the filelog to find wanted revisions
2130
2131
2131 try:
2132 try:
2132 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2133 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2133 except FileWalkError:
2134 except FileWalkError:
2134 slowpath = True
2135 slowpath = True
2135
2136
2136 # We decided to fall back to the slowpath because at least one
2137 # We decided to fall back to the slowpath because at least one
2137 # of the paths was not a file. Check to see if at least one of them
2138 # of the paths was not a file. Check to see if at least one of them
2138 # existed in history, otherwise simply return
2139 # existed in history, otherwise simply return
2139 for path in match.files():
2140 for path in match.files():
2140 if path == '.' or path in repo.store:
2141 if path == '.' or path in repo.store:
2141 break
2142 break
2142 else:
2143 else:
2143 return []
2144 return []
2144
2145
2145 if slowpath:
2146 if slowpath:
2146 # We have to read the changelog to match filenames against
2147 # We have to read the changelog to match filenames against
2147 # changed files
2148 # changed files
2148
2149
2149 if follow:
2150 if follow:
2150 raise error.Abort(_('can only follow copies/renames for explicit '
2151 raise error.Abort(_('can only follow copies/renames for explicit '
2151 'filenames'))
2152 'filenames'))
2152
2153
2153 # The slow path checks files modified in every changeset.
2154 # The slow path checks files modified in every changeset.
2154 # This is really slow on large repos, so compute the set lazily.
2155 # This is really slow on large repos, so compute the set lazily.
2155 class lazywantedset(object):
2156 class lazywantedset(object):
2156 def __init__(self):
2157 def __init__(self):
2157 self.set = set()
2158 self.set = set()
2158 self.revs = set(revs)
2159 self.revs = set(revs)
2159
2160
2160 # No need to worry about locality here because it will be accessed
2161 # No need to worry about locality here because it will be accessed
2161 # in the same order as the increasing window below.
2162 # in the same order as the increasing window below.
2162 def __contains__(self, value):
2163 def __contains__(self, value):
2163 if value in self.set:
2164 if value in self.set:
2164 return True
2165 return True
2165 elif not value in self.revs:
2166 elif not value in self.revs:
2166 return False
2167 return False
2167 else:
2168 else:
2168 self.revs.discard(value)
2169 self.revs.discard(value)
2169 ctx = change(value)
2170 ctx = change(value)
2170 matches = filter(match, ctx.files())
2171 matches = filter(match, ctx.files())
2171 if matches:
2172 if matches:
2172 fncache[value] = matches
2173 fncache[value] = matches
2173 self.set.add(value)
2174 self.set.add(value)
2174 return True
2175 return True
2175 return False
2176 return False
2176
2177
2177 def discard(self, value):
2178 def discard(self, value):
2178 self.revs.discard(value)
2179 self.revs.discard(value)
2179 self.set.discard(value)
2180 self.set.discard(value)
2180
2181
2181 wanted = lazywantedset()
2182 wanted = lazywantedset()
2182
2183
2183 # it might be worthwhile to do this in the iterator if the rev range
2184 # it might be worthwhile to do this in the iterator if the rev range
2184 # is descending and the prune args are all within that range
2185 # is descending and the prune args are all within that range
2185 for rev in opts.get('prune', ()):
2186 for rev in opts.get('prune', ()):
2186 rev = repo[rev].rev()
2187 rev = repo[rev].rev()
2187 ff = _followfilter(repo)
2188 ff = _followfilter(repo)
2188 stop = min(revs[0], revs[-1])
2189 stop = min(revs[0], revs[-1])
2189 for x in xrange(rev, stop - 1, -1):
2190 for x in xrange(rev, stop - 1, -1):
2190 if ff.match(x):
2191 if ff.match(x):
2191 wanted = wanted - [x]
2192 wanted = wanted - [x]
2192
2193
2193 # Now that wanted is correctly initialized, we can iterate over the
2194 # Now that wanted is correctly initialized, we can iterate over the
2194 # revision range, yielding only revisions in wanted.
2195 # revision range, yielding only revisions in wanted.
2195 def iterate():
2196 def iterate():
2196 if follow and match.always():
2197 if follow and match.always():
2197 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
2198 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
2198 def want(rev):
2199 def want(rev):
2199 return ff.match(rev) and rev in wanted
2200 return ff.match(rev) and rev in wanted
2200 else:
2201 else:
2201 def want(rev):
2202 def want(rev):
2202 return rev in wanted
2203 return rev in wanted
2203
2204
2204 it = iter(revs)
2205 it = iter(revs)
2205 stopiteration = False
2206 stopiteration = False
2206 for windowsize in increasingwindows():
2207 for windowsize in increasingwindows():
2207 nrevs = []
2208 nrevs = []
2208 for i in xrange(windowsize):
2209 for i in xrange(windowsize):
2209 rev = next(it, None)
2210 rev = next(it, None)
2210 if rev is None:
2211 if rev is None:
2211 stopiteration = True
2212 stopiteration = True
2212 break
2213 break
2213 elif want(rev):
2214 elif want(rev):
2214 nrevs.append(rev)
2215 nrevs.append(rev)
2215 for rev in sorted(nrevs):
2216 for rev in sorted(nrevs):
2216 fns = fncache.get(rev)
2217 fns = fncache.get(rev)
2217 ctx = change(rev)
2218 ctx = change(rev)
2218 if not fns:
2219 if not fns:
2219 def fns_generator():
2220 def fns_generator():
2220 for f in ctx.files():
2221 for f in ctx.files():
2221 if match(f):
2222 if match(f):
2222 yield f
2223 yield f
2223 fns = fns_generator()
2224 fns = fns_generator()
2224 prepare(ctx, fns)
2225 prepare(ctx, fns)
2225 for rev in nrevs:
2226 for rev in nrevs:
2226 yield change(rev)
2227 yield change(rev)
2227
2228
2228 if stopiteration:
2229 if stopiteration:
2229 break
2230 break
2230
2231
2231 return iterate()
2232 return iterate()
2232
2233
2233 def _makefollowlogfilematcher(repo, files, followfirst):
2234 def _makefollowlogfilematcher(repo, files, followfirst):
2234 # When displaying a revision with --patch --follow FILE, we have
2235 # When displaying a revision with --patch --follow FILE, we have
2235 # to know which file of the revision must be diffed. With
2236 # to know which file of the revision must be diffed. With
2236 # --follow, we want the names of the ancestors of FILE in the
2237 # --follow, we want the names of the ancestors of FILE in the
2237 # revision, stored in "fcache". "fcache" is populated by
2238 # revision, stored in "fcache". "fcache" is populated by
2238 # reproducing the graph traversal already done by --follow revset
2239 # reproducing the graph traversal already done by --follow revset
2239 # and relating revs to file names (which is not "correct" but
2240 # and relating revs to file names (which is not "correct" but
2240 # good enough).
2241 # good enough).
2241 fcache = {}
2242 fcache = {}
2242 fcacheready = [False]
2243 fcacheready = [False]
2243 pctx = repo['.']
2244 pctx = repo['.']
2244
2245
2245 def populate():
2246 def populate():
2246 for fn in files:
2247 for fn in files:
2247 fctx = pctx[fn]
2248 fctx = pctx[fn]
2248 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
2249 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
2249 for c in fctx.ancestors(followfirst=followfirst):
2250 for c in fctx.ancestors(followfirst=followfirst):
2250 fcache.setdefault(c.rev(), set()).add(c.path())
2251 fcache.setdefault(c.rev(), set()).add(c.path())
2251
2252
2252 def filematcher(rev):
2253 def filematcher(rev):
2253 if not fcacheready[0]:
2254 if not fcacheready[0]:
2254 # Lazy initialization
2255 # Lazy initialization
2255 fcacheready[0] = True
2256 fcacheready[0] = True
2256 populate()
2257 populate()
2257 return scmutil.matchfiles(repo, fcache.get(rev, []))
2258 return scmutil.matchfiles(repo, fcache.get(rev, []))
2258
2259
2259 return filematcher
2260 return filematcher
2260
2261
2261 def _makenofollowlogfilematcher(repo, pats, opts):
2262 def _makenofollowlogfilematcher(repo, pats, opts):
2262 '''hook for extensions to override the filematcher for non-follow cases'''
2263 '''hook for extensions to override the filematcher for non-follow cases'''
2263 return None
2264 return None
2264
2265
2265 def _makelogrevset(repo, pats, opts, revs):
2266 def _makelogrevset(repo, pats, opts, revs):
2266 """Return (expr, filematcher) where expr is a revset string built
2267 """Return (expr, filematcher) where expr is a revset string built
2267 from log options and file patterns or None. If --stat or --patch
2268 from log options and file patterns or None. If --stat or --patch
2268 are not passed filematcher is None. Otherwise it is a callable
2269 are not passed filematcher is None. Otherwise it is a callable
2269 taking a revision number and returning a match objects filtering
2270 taking a revision number and returning a match objects filtering
2270 the files to be detailed when displaying the revision.
2271 the files to be detailed when displaying the revision.
2271 """
2272 """
2272 opt2revset = {
2273 opt2revset = {
2273 'no_merges': ('not merge()', None),
2274 'no_merges': ('not merge()', None),
2274 'only_merges': ('merge()', None),
2275 'only_merges': ('merge()', None),
2275 '_ancestors': ('ancestors(%(val)s)', None),
2276 '_ancestors': ('ancestors(%(val)s)', None),
2276 '_fancestors': ('_firstancestors(%(val)s)', None),
2277 '_fancestors': ('_firstancestors(%(val)s)', None),
2277 '_descendants': ('descendants(%(val)s)', None),
2278 '_descendants': ('descendants(%(val)s)', None),
2278 '_fdescendants': ('_firstdescendants(%(val)s)', None),
2279 '_fdescendants': ('_firstdescendants(%(val)s)', None),
2279 '_matchfiles': ('_matchfiles(%(val)s)', None),
2280 '_matchfiles': ('_matchfiles(%(val)s)', None),
2280 'date': ('date(%(val)r)', None),
2281 'date': ('date(%(val)r)', None),
2281 'branch': ('branch(%(val)r)', ' or '),
2282 'branch': ('branch(%(val)r)', ' or '),
2282 '_patslog': ('filelog(%(val)r)', ' or '),
2283 '_patslog': ('filelog(%(val)r)', ' or '),
2283 '_patsfollow': ('follow(%(val)r)', ' or '),
2284 '_patsfollow': ('follow(%(val)r)', ' or '),
2284 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
2285 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
2285 'keyword': ('keyword(%(val)r)', ' or '),
2286 'keyword': ('keyword(%(val)r)', ' or '),
2286 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
2287 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
2287 'user': ('user(%(val)r)', ' or '),
2288 'user': ('user(%(val)r)', ' or '),
2288 }
2289 }
2289
2290
2290 opts = dict(opts)
2291 opts = dict(opts)
2291 # follow or not follow?
2292 # follow or not follow?
2292 follow = opts.get('follow') or opts.get('follow_first')
2293 follow = opts.get('follow') or opts.get('follow_first')
2293 if opts.get('follow_first'):
2294 if opts.get('follow_first'):
2294 followfirst = 1
2295 followfirst = 1
2295 else:
2296 else:
2296 followfirst = 0
2297 followfirst = 0
2297 # --follow with FILE behavior depends on revs...
2298 # --follow with FILE behavior depends on revs...
2298 it = iter(revs)
2299 it = iter(revs)
2299 startrev = next(it)
2300 startrev = next(it)
2300 followdescendants = startrev < next(it, startrev)
2301 followdescendants = startrev < next(it, startrev)
2301
2302
2302 # branch and only_branch are really aliases and must be handled at
2303 # branch and only_branch are really aliases and must be handled at
2303 # the same time
2304 # the same time
2304 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
2305 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
2305 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
2306 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
2306 # pats/include/exclude are passed to match.match() directly in
2307 # pats/include/exclude are passed to match.match() directly in
2307 # _matchfiles() revset but walkchangerevs() builds its matcher with
2308 # _matchfiles() revset but walkchangerevs() builds its matcher with
2308 # scmutil.match(). The difference is input pats are globbed on
2309 # scmutil.match(). The difference is input pats are globbed on
2309 # platforms without shell expansion (windows).
2310 # platforms without shell expansion (windows).
2310 wctx = repo[None]
2311 wctx = repo[None]
2311 match, pats = scmutil.matchandpats(wctx, pats, opts)
2312 match, pats = scmutil.matchandpats(wctx, pats, opts)
2312 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2313 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2313 opts.get('removed'))
2314 opts.get('removed'))
2314 if not slowpath:
2315 if not slowpath:
2315 for f in match.files():
2316 for f in match.files():
2316 if follow and f not in wctx:
2317 if follow and f not in wctx:
2317 # If the file exists, it may be a directory, so let it
2318 # If the file exists, it may be a directory, so let it
2318 # take the slow path.
2319 # take the slow path.
2319 if os.path.exists(repo.wjoin(f)):
2320 if os.path.exists(repo.wjoin(f)):
2320 slowpath = True
2321 slowpath = True
2321 continue
2322 continue
2322 else:
2323 else:
2323 raise error.Abort(_('cannot follow file not in parent '
2324 raise error.Abort(_('cannot follow file not in parent '
2324 'revision: "%s"') % f)
2325 'revision: "%s"') % f)
2325 filelog = repo.file(f)
2326 filelog = repo.file(f)
2326 if not filelog:
2327 if not filelog:
2327 # A zero count may be a directory or deleted file, so
2328 # A zero count may be a directory or deleted file, so
2328 # try to find matching entries on the slow path.
2329 # try to find matching entries on the slow path.
2329 if follow:
2330 if follow:
2330 raise error.Abort(
2331 raise error.Abort(
2331 _('cannot follow nonexistent file: "%s"') % f)
2332 _('cannot follow nonexistent file: "%s"') % f)
2332 slowpath = True
2333 slowpath = True
2333
2334
2334 # We decided to fall back to the slowpath because at least one
2335 # We decided to fall back to the slowpath because at least one
2335 # of the paths was not a file. Check to see if at least one of them
2336 # of the paths was not a file. Check to see if at least one of them
2336 # existed in history - in that case, we'll continue down the
2337 # existed in history - in that case, we'll continue down the
2337 # slowpath; otherwise, we can turn off the slowpath
2338 # slowpath; otherwise, we can turn off the slowpath
2338 if slowpath:
2339 if slowpath:
2339 for path in match.files():
2340 for path in match.files():
2340 if path == '.' or path in repo.store:
2341 if path == '.' or path in repo.store:
2341 break
2342 break
2342 else:
2343 else:
2343 slowpath = False
2344 slowpath = False
2344
2345
2345 fpats = ('_patsfollow', '_patsfollowfirst')
2346 fpats = ('_patsfollow', '_patsfollowfirst')
2346 fnopats = (('_ancestors', '_fancestors'),
2347 fnopats = (('_ancestors', '_fancestors'),
2347 ('_descendants', '_fdescendants'))
2348 ('_descendants', '_fdescendants'))
2348 if slowpath:
2349 if slowpath:
2349 # See walkchangerevs() slow path.
2350 # See walkchangerevs() slow path.
2350 #
2351 #
2351 # pats/include/exclude cannot be represented as separate
2352 # pats/include/exclude cannot be represented as separate
2352 # revset expressions as their filtering logic applies at file
2353 # revset expressions as their filtering logic applies at file
2353 # level. For instance "-I a -X a" matches a revision touching
2354 # level. For instance "-I a -X a" matches a revision touching
2354 # "a" and "b" while "file(a) and not file(b)" does
2355 # "a" and "b" while "file(a) and not file(b)" does
2355 # not. Besides, filesets are evaluated against the working
2356 # not. Besides, filesets are evaluated against the working
2356 # directory.
2357 # directory.
2357 matchargs = ['r:', 'd:relpath']
2358 matchargs = ['r:', 'd:relpath']
2358 for p in pats:
2359 for p in pats:
2359 matchargs.append('p:' + p)
2360 matchargs.append('p:' + p)
2360 for p in opts.get('include', []):
2361 for p in opts.get('include', []):
2361 matchargs.append('i:' + p)
2362 matchargs.append('i:' + p)
2362 for p in opts.get('exclude', []):
2363 for p in opts.get('exclude', []):
2363 matchargs.append('x:' + p)
2364 matchargs.append('x:' + p)
2364 matchargs = ','.join(('%r' % p) for p in matchargs)
2365 matchargs = ','.join(('%r' % p) for p in matchargs)
2365 opts['_matchfiles'] = matchargs
2366 opts['_matchfiles'] = matchargs
2366 if follow:
2367 if follow:
2367 opts[fnopats[0][followfirst]] = '.'
2368 opts[fnopats[0][followfirst]] = '.'
2368 else:
2369 else:
2369 if follow:
2370 if follow:
2370 if pats:
2371 if pats:
2371 # follow() revset interprets its file argument as a
2372 # follow() revset interprets its file argument as a
2372 # manifest entry, so use match.files(), not pats.
2373 # manifest entry, so use match.files(), not pats.
2373 opts[fpats[followfirst]] = list(match.files())
2374 opts[fpats[followfirst]] = list(match.files())
2374 else:
2375 else:
2375 op = fnopats[followdescendants][followfirst]
2376 op = fnopats[followdescendants][followfirst]
2376 opts[op] = 'rev(%d)' % startrev
2377 opts[op] = 'rev(%d)' % startrev
2377 else:
2378 else:
2378 opts['_patslog'] = list(pats)
2379 opts['_patslog'] = list(pats)
2379
2380
2380 filematcher = None
2381 filematcher = None
2381 if opts.get('patch') or opts.get('stat'):
2382 if opts.get('patch') or opts.get('stat'):
2382 # When following files, track renames via a special matcher.
2383 # When following files, track renames via a special matcher.
2383 # If we're forced to take the slowpath it means we're following
2384 # If we're forced to take the slowpath it means we're following
2384 # at least one pattern/directory, so don't bother with rename tracking.
2385 # at least one pattern/directory, so don't bother with rename tracking.
2385 if follow and not match.always() and not slowpath:
2386 if follow and not match.always() and not slowpath:
2386 # _makefollowlogfilematcher expects its files argument to be
2387 # _makefollowlogfilematcher expects its files argument to be
2387 # relative to the repo root, so use match.files(), not pats.
2388 # relative to the repo root, so use match.files(), not pats.
2388 filematcher = _makefollowlogfilematcher(repo, match.files(),
2389 filematcher = _makefollowlogfilematcher(repo, match.files(),
2389 followfirst)
2390 followfirst)
2390 else:
2391 else:
2391 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2392 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2392 if filematcher is None:
2393 if filematcher is None:
2393 filematcher = lambda rev: match
2394 filematcher = lambda rev: match
2394
2395
2395 expr = []
2396 expr = []
2396 for op, val in sorted(opts.iteritems()):
2397 for op, val in sorted(opts.iteritems()):
2397 if not val:
2398 if not val:
2398 continue
2399 continue
2399 if op not in opt2revset:
2400 if op not in opt2revset:
2400 continue
2401 continue
2401 revop, andor = opt2revset[op]
2402 revop, andor = opt2revset[op]
2402 if '%(val)' not in revop:
2403 if '%(val)' not in revop:
2403 expr.append(revop)
2404 expr.append(revop)
2404 else:
2405 else:
2405 if not isinstance(val, list):
2406 if not isinstance(val, list):
2406 e = revop % {'val': val}
2407 e = revop % {'val': val}
2407 else:
2408 else:
2408 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2409 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2409 expr.append(e)
2410 expr.append(e)
2410
2411
2411 if expr:
2412 if expr:
2412 expr = '(' + ' and '.join(expr) + ')'
2413 expr = '(' + ' and '.join(expr) + ')'
2413 else:
2414 else:
2414 expr = None
2415 expr = None
2415 return expr, filematcher
2416 return expr, filematcher
2416
2417
2417 def _logrevs(repo, opts):
2418 def _logrevs(repo, opts):
2418 # Default --rev value depends on --follow but --follow behavior
2419 # Default --rev value depends on --follow but --follow behavior
2419 # depends on revisions resolved from --rev...
2420 # depends on revisions resolved from --rev...
2420 follow = opts.get('follow') or opts.get('follow_first')
2421 follow = opts.get('follow') or opts.get('follow_first')
2421 if opts.get('rev'):
2422 if opts.get('rev'):
2422 revs = scmutil.revrange(repo, opts['rev'])
2423 revs = scmutil.revrange(repo, opts['rev'])
2423 elif follow and repo.dirstate.p1() == nullid:
2424 elif follow and repo.dirstate.p1() == nullid:
2424 revs = smartset.baseset()
2425 revs = smartset.baseset()
2425 elif follow:
2426 elif follow:
2426 revs = repo.revs('reverse(:.)')
2427 revs = repo.revs('reverse(:.)')
2427 else:
2428 else:
2428 revs = smartset.spanset(repo)
2429 revs = smartset.spanset(repo)
2429 revs.reverse()
2430 revs.reverse()
2430 return revs
2431 return revs
2431
2432
2432 def getgraphlogrevs(repo, pats, opts):
2433 def getgraphlogrevs(repo, pats, opts):
2433 """Return (revs, expr, filematcher) where revs is an iterable of
2434 """Return (revs, expr, filematcher) where revs is an iterable of
2434 revision numbers, expr is a revset string built from log options
2435 revision numbers, expr is a revset string built from log options
2435 and file patterns or None, and used to filter 'revs'. If --stat or
2436 and file patterns or None, and used to filter 'revs'. If --stat or
2436 --patch are not passed filematcher is None. Otherwise it is a
2437 --patch are not passed filematcher is None. Otherwise it is a
2437 callable taking a revision number and returning a match objects
2438 callable taking a revision number and returning a match objects
2438 filtering the files to be detailed when displaying the revision.
2439 filtering the files to be detailed when displaying the revision.
2439 """
2440 """
2440 limit = loglimit(opts)
2441 limit = loglimit(opts)
2441 revs = _logrevs(repo, opts)
2442 revs = _logrevs(repo, opts)
2442 if not revs:
2443 if not revs:
2443 return smartset.baseset(), None, None
2444 return smartset.baseset(), None, None
2444 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2445 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2445 if opts.get('rev'):
2446 if opts.get('rev'):
2446 # User-specified revs might be unsorted, but don't sort before
2447 # User-specified revs might be unsorted, but don't sort before
2447 # _makelogrevset because it might depend on the order of revs
2448 # _makelogrevset because it might depend on the order of revs
2448 if not (revs.isdescending() or revs.istopo()):
2449 if not (revs.isdescending() or revs.istopo()):
2449 revs.sort(reverse=True)
2450 revs.sort(reverse=True)
2450 if expr:
2451 if expr:
2451 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2452 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2452 revs = matcher(repo, revs)
2453 revs = matcher(repo, revs)
2453 if limit is not None:
2454 if limit is not None:
2454 limitedrevs = []
2455 limitedrevs = []
2455 for idx, rev in enumerate(revs):
2456 for idx, rev in enumerate(revs):
2456 if idx >= limit:
2457 if idx >= limit:
2457 break
2458 break
2458 limitedrevs.append(rev)
2459 limitedrevs.append(rev)
2459 revs = smartset.baseset(limitedrevs)
2460 revs = smartset.baseset(limitedrevs)
2460
2461
2461 return revs, expr, filematcher
2462 return revs, expr, filematcher
2462
2463
2463 def getlogrevs(repo, pats, opts):
2464 def getlogrevs(repo, pats, opts):
2464 """Return (revs, expr, filematcher) where revs is an iterable of
2465 """Return (revs, expr, filematcher) where revs is an iterable of
2465 revision numbers, expr is a revset string built from log options
2466 revision numbers, expr is a revset string built from log options
2466 and file patterns or None, and used to filter 'revs'. If --stat or
2467 and file patterns or None, and used to filter 'revs'. If --stat or
2467 --patch are not passed filematcher is None. Otherwise it is a
2468 --patch are not passed filematcher is None. Otherwise it is a
2468 callable taking a revision number and returning a match objects
2469 callable taking a revision number and returning a match objects
2469 filtering the files to be detailed when displaying the revision.
2470 filtering the files to be detailed when displaying the revision.
2470 """
2471 """
2471 limit = loglimit(opts)
2472 limit = loglimit(opts)
2472 revs = _logrevs(repo, opts)
2473 revs = _logrevs(repo, opts)
2473 if not revs:
2474 if not revs:
2474 return smartset.baseset([]), None, None
2475 return smartset.baseset([]), None, None
2475 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2476 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2476 if expr:
2477 if expr:
2477 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2478 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2478 revs = matcher(repo, revs)
2479 revs = matcher(repo, revs)
2479 if limit is not None:
2480 if limit is not None:
2480 limitedrevs = []
2481 limitedrevs = []
2481 for idx, r in enumerate(revs):
2482 for idx, r in enumerate(revs):
2482 if limit <= idx:
2483 if limit <= idx:
2483 break
2484 break
2484 limitedrevs.append(r)
2485 limitedrevs.append(r)
2485 revs = smartset.baseset(limitedrevs)
2486 revs = smartset.baseset(limitedrevs)
2486
2487
2487 return revs, expr, filematcher
2488 return revs, expr, filematcher
2488
2489
2489 def _graphnodeformatter(ui, displayer):
2490 def _graphnodeformatter(ui, displayer):
2490 spec = ui.config('ui', 'graphnodetemplate')
2491 spec = ui.config('ui', 'graphnodetemplate')
2491 if not spec:
2492 if not spec:
2492 return templatekw.showgraphnode # fast path for "{graphnode}"
2493 return templatekw.showgraphnode # fast path for "{graphnode}"
2493
2494
2494 spec = templater.unquotestring(spec)
2495 spec = templater.unquotestring(spec)
2495 templ = formatter.maketemplater(ui, spec)
2496 templ = formatter.maketemplater(ui, spec)
2496 cache = {}
2497 cache = {}
2497 if isinstance(displayer, changeset_templater):
2498 if isinstance(displayer, changeset_templater):
2498 cache = displayer.cache # reuse cache of slow templates
2499 cache = displayer.cache # reuse cache of slow templates
2499 props = templatekw.keywords.copy()
2500 props = templatekw.keywords.copy()
2500 props['templ'] = templ
2501 props['templ'] = templ
2501 props['cache'] = cache
2502 props['cache'] = cache
2502 def formatnode(repo, ctx):
2503 def formatnode(repo, ctx):
2503 props['ctx'] = ctx
2504 props['ctx'] = ctx
2504 props['repo'] = repo
2505 props['repo'] = repo
2505 props['ui'] = repo.ui
2506 props['ui'] = repo.ui
2506 props['revcache'] = {}
2507 props['revcache'] = {}
2507 return templ.render(props)
2508 return templ.render(props)
2508 return formatnode
2509 return formatnode
2509
2510
2510 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2511 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2511 filematcher=None):
2512 filematcher=None):
2512 formatnode = _graphnodeformatter(ui, displayer)
2513 formatnode = _graphnodeformatter(ui, displayer)
2513 state = graphmod.asciistate()
2514 state = graphmod.asciistate()
2514 styles = state['styles']
2515 styles = state['styles']
2515
2516
2516 # only set graph styling if HGPLAIN is not set.
2517 # only set graph styling if HGPLAIN is not set.
2517 if ui.plain('graph'):
2518 if ui.plain('graph'):
2518 # set all edge styles to |, the default pre-3.8 behaviour
2519 # set all edge styles to |, the default pre-3.8 behaviour
2519 styles.update(dict.fromkeys(styles, '|'))
2520 styles.update(dict.fromkeys(styles, '|'))
2520 else:
2521 else:
2521 edgetypes = {
2522 edgetypes = {
2522 'parent': graphmod.PARENT,
2523 'parent': graphmod.PARENT,
2523 'grandparent': graphmod.GRANDPARENT,
2524 'grandparent': graphmod.GRANDPARENT,
2524 'missing': graphmod.MISSINGPARENT
2525 'missing': graphmod.MISSINGPARENT
2525 }
2526 }
2526 for name, key in edgetypes.items():
2527 for name, key in edgetypes.items():
2527 # experimental config: experimental.graphstyle.*
2528 # experimental config: experimental.graphstyle.*
2528 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2529 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2529 styles[key])
2530 styles[key])
2530 if not styles[key]:
2531 if not styles[key]:
2531 styles[key] = None
2532 styles[key] = None
2532
2533
2533 # experimental config: experimental.graphshorten
2534 # experimental config: experimental.graphshorten
2534 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2535 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2535
2536
2536 for rev, type, ctx, parents in dag:
2537 for rev, type, ctx, parents in dag:
2537 char = formatnode(repo, ctx)
2538 char = formatnode(repo, ctx)
2538 copies = None
2539 copies = None
2539 if getrenamed and ctx.rev():
2540 if getrenamed and ctx.rev():
2540 copies = []
2541 copies = []
2541 for fn in ctx.files():
2542 for fn in ctx.files():
2542 rename = getrenamed(fn, ctx.rev())
2543 rename = getrenamed(fn, ctx.rev())
2543 if rename:
2544 if rename:
2544 copies.append((fn, rename[0]))
2545 copies.append((fn, rename[0]))
2545 revmatchfn = None
2546 revmatchfn = None
2546 if filematcher is not None:
2547 if filematcher is not None:
2547 revmatchfn = filematcher(ctx.rev())
2548 revmatchfn = filematcher(ctx.rev())
2548 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2549 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2549 lines = displayer.hunk.pop(rev).split('\n')
2550 lines = displayer.hunk.pop(rev).split('\n')
2550 if not lines[-1]:
2551 if not lines[-1]:
2551 del lines[-1]
2552 del lines[-1]
2552 displayer.flush(ctx)
2553 displayer.flush(ctx)
2553 edges = edgefn(type, char, lines, state, rev, parents)
2554 edges = edgefn(type, char, lines, state, rev, parents)
2554 for type, char, lines, coldata in edges:
2555 for type, char, lines, coldata in edges:
2555 graphmod.ascii(ui, state, type, char, lines, coldata)
2556 graphmod.ascii(ui, state, type, char, lines, coldata)
2556 displayer.close()
2557 displayer.close()
2557
2558
2558 def graphlog(ui, repo, pats, opts):
2559 def graphlog(ui, repo, pats, opts):
2559 # Parameters are identical to log command ones
2560 # Parameters are identical to log command ones
2560 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2561 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2561 revdag = graphmod.dagwalker(repo, revs)
2562 revdag = graphmod.dagwalker(repo, revs)
2562
2563
2563 getrenamed = None
2564 getrenamed = None
2564 if opts.get('copies'):
2565 if opts.get('copies'):
2565 endrev = None
2566 endrev = None
2566 if opts.get('rev'):
2567 if opts.get('rev'):
2567 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2568 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2568 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2569 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2569
2570
2570 ui.pager('log')
2571 ui.pager('log')
2571 displayer = show_changeset(ui, repo, opts, buffered=True)
2572 displayer = show_changeset(ui, repo, opts, buffered=True)
2572 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2573 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2573 filematcher)
2574 filematcher)
2574
2575
2575 def checkunsupportedgraphflags(pats, opts):
2576 def checkunsupportedgraphflags(pats, opts):
2576 for op in ["newest_first"]:
2577 for op in ["newest_first"]:
2577 if op in opts and opts[op]:
2578 if op in opts and opts[op]:
2578 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2579 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2579 % op.replace("_", "-"))
2580 % op.replace("_", "-"))
2580
2581
2581 def graphrevs(repo, nodes, opts):
2582 def graphrevs(repo, nodes, opts):
2582 limit = loglimit(opts)
2583 limit = loglimit(opts)
2583 nodes.reverse()
2584 nodes.reverse()
2584 if limit is not None:
2585 if limit is not None:
2585 nodes = nodes[:limit]
2586 nodes = nodes[:limit]
2586 return graphmod.nodes(repo, nodes)
2587 return graphmod.nodes(repo, nodes)
2587
2588
2588 def add(ui, repo, match, prefix, explicitonly, **opts):
2589 def add(ui, repo, match, prefix, explicitonly, **opts):
2589 join = lambda f: os.path.join(prefix, f)
2590 join = lambda f: os.path.join(prefix, f)
2590 bad = []
2591 bad = []
2591
2592
2592 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2593 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2593 names = []
2594 names = []
2594 wctx = repo[None]
2595 wctx = repo[None]
2595 cca = None
2596 cca = None
2596 abort, warn = scmutil.checkportabilityalert(ui)
2597 abort, warn = scmutil.checkportabilityalert(ui)
2597 if abort or warn:
2598 if abort or warn:
2598 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2599 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2599
2600
2600 badmatch = matchmod.badmatch(match, badfn)
2601 badmatch = matchmod.badmatch(match, badfn)
2601 dirstate = repo.dirstate
2602 dirstate = repo.dirstate
2602 # We don't want to just call wctx.walk here, since it would return a lot of
2603 # We don't want to just call wctx.walk here, since it would return a lot of
2603 # clean files, which we aren't interested in and takes time.
2604 # clean files, which we aren't interested in and takes time.
2604 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2605 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2605 True, False, full=False)):
2606 True, False, full=False)):
2606 exact = match.exact(f)
2607 exact = match.exact(f)
2607 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2608 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2608 if cca:
2609 if cca:
2609 cca(f)
2610 cca(f)
2610 names.append(f)
2611 names.append(f)
2611 if ui.verbose or not exact:
2612 if ui.verbose or not exact:
2612 ui.status(_('adding %s\n') % match.rel(f))
2613 ui.status(_('adding %s\n') % match.rel(f))
2613
2614
2614 for subpath in sorted(wctx.substate):
2615 for subpath in sorted(wctx.substate):
2615 sub = wctx.sub(subpath)
2616 sub = wctx.sub(subpath)
2616 try:
2617 try:
2617 submatch = matchmod.subdirmatcher(subpath, match)
2618 submatch = matchmod.subdirmatcher(subpath, match)
2618 if opts.get(r'subrepos'):
2619 if opts.get(r'subrepos'):
2619 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2620 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2620 else:
2621 else:
2621 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2622 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2622 except error.LookupError:
2623 except error.LookupError:
2623 ui.status(_("skipping missing subrepository: %s\n")
2624 ui.status(_("skipping missing subrepository: %s\n")
2624 % join(subpath))
2625 % join(subpath))
2625
2626
2626 if not opts.get(r'dry_run'):
2627 if not opts.get(r'dry_run'):
2627 rejected = wctx.add(names, prefix)
2628 rejected = wctx.add(names, prefix)
2628 bad.extend(f for f in rejected if f in match.files())
2629 bad.extend(f for f in rejected if f in match.files())
2629 return bad
2630 return bad
2630
2631
2631 def addwebdirpath(repo, serverpath, webconf):
2632 def addwebdirpath(repo, serverpath, webconf):
2632 webconf[serverpath] = repo.root
2633 webconf[serverpath] = repo.root
2633 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2634 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2634
2635
2635 for r in repo.revs('filelog("path:.hgsub")'):
2636 for r in repo.revs('filelog("path:.hgsub")'):
2636 ctx = repo[r]
2637 ctx = repo[r]
2637 for subpath in ctx.substate:
2638 for subpath in ctx.substate:
2638 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2639 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2639
2640
2640 def forget(ui, repo, match, prefix, explicitonly):
2641 def forget(ui, repo, match, prefix, explicitonly):
2641 join = lambda f: os.path.join(prefix, f)
2642 join = lambda f: os.path.join(prefix, f)
2642 bad = []
2643 bad = []
2643 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2644 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2644 wctx = repo[None]
2645 wctx = repo[None]
2645 forgot = []
2646 forgot = []
2646
2647
2647 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2648 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2648 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2649 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2649 if explicitonly:
2650 if explicitonly:
2650 forget = [f for f in forget if match.exact(f)]
2651 forget = [f for f in forget if match.exact(f)]
2651
2652
2652 for subpath in sorted(wctx.substate):
2653 for subpath in sorted(wctx.substate):
2653 sub = wctx.sub(subpath)
2654 sub = wctx.sub(subpath)
2654 try:
2655 try:
2655 submatch = matchmod.subdirmatcher(subpath, match)
2656 submatch = matchmod.subdirmatcher(subpath, match)
2656 subbad, subforgot = sub.forget(submatch, prefix)
2657 subbad, subforgot = sub.forget(submatch, prefix)
2657 bad.extend([subpath + '/' + f for f in subbad])
2658 bad.extend([subpath + '/' + f for f in subbad])
2658 forgot.extend([subpath + '/' + f for f in subforgot])
2659 forgot.extend([subpath + '/' + f for f in subforgot])
2659 except error.LookupError:
2660 except error.LookupError:
2660 ui.status(_("skipping missing subrepository: %s\n")
2661 ui.status(_("skipping missing subrepository: %s\n")
2661 % join(subpath))
2662 % join(subpath))
2662
2663
2663 if not explicitonly:
2664 if not explicitonly:
2664 for f in match.files():
2665 for f in match.files():
2665 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2666 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2666 if f not in forgot:
2667 if f not in forgot:
2667 if repo.wvfs.exists(f):
2668 if repo.wvfs.exists(f):
2668 # Don't complain if the exact case match wasn't given.
2669 # Don't complain if the exact case match wasn't given.
2669 # But don't do this until after checking 'forgot', so
2670 # But don't do this until after checking 'forgot', so
2670 # that subrepo files aren't normalized, and this op is
2671 # that subrepo files aren't normalized, and this op is
2671 # purely from data cached by the status walk above.
2672 # purely from data cached by the status walk above.
2672 if repo.dirstate.normalize(f) in repo.dirstate:
2673 if repo.dirstate.normalize(f) in repo.dirstate:
2673 continue
2674 continue
2674 ui.warn(_('not removing %s: '
2675 ui.warn(_('not removing %s: '
2675 'file is already untracked\n')
2676 'file is already untracked\n')
2676 % match.rel(f))
2677 % match.rel(f))
2677 bad.append(f)
2678 bad.append(f)
2678
2679
2679 for f in forget:
2680 for f in forget:
2680 if ui.verbose or not match.exact(f):
2681 if ui.verbose or not match.exact(f):
2681 ui.status(_('removing %s\n') % match.rel(f))
2682 ui.status(_('removing %s\n') % match.rel(f))
2682
2683
2683 rejected = wctx.forget(forget, prefix)
2684 rejected = wctx.forget(forget, prefix)
2684 bad.extend(f for f in rejected if f in match.files())
2685 bad.extend(f for f in rejected if f in match.files())
2685 forgot.extend(f for f in forget if f not in rejected)
2686 forgot.extend(f for f in forget if f not in rejected)
2686 return bad, forgot
2687 return bad, forgot
2687
2688
2688 def files(ui, ctx, m, fm, fmt, subrepos):
2689 def files(ui, ctx, m, fm, fmt, subrepos):
2689 rev = ctx.rev()
2690 rev = ctx.rev()
2690 ret = 1
2691 ret = 1
2691 ds = ctx.repo().dirstate
2692 ds = ctx.repo().dirstate
2692
2693
2693 for f in ctx.matches(m):
2694 for f in ctx.matches(m):
2694 if rev is None and ds[f] == 'r':
2695 if rev is None and ds[f] == 'r':
2695 continue
2696 continue
2696 fm.startitem()
2697 fm.startitem()
2697 if ui.verbose:
2698 if ui.verbose:
2698 fc = ctx[f]
2699 fc = ctx[f]
2699 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2700 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2700 fm.data(abspath=f)
2701 fm.data(abspath=f)
2701 fm.write('path', fmt, m.rel(f))
2702 fm.write('path', fmt, m.rel(f))
2702 ret = 0
2703 ret = 0
2703
2704
2704 for subpath in sorted(ctx.substate):
2705 for subpath in sorted(ctx.substate):
2705 submatch = matchmod.subdirmatcher(subpath, m)
2706 submatch = matchmod.subdirmatcher(subpath, m)
2706 if (subrepos or m.exact(subpath) or any(submatch.files())):
2707 if (subrepos or m.exact(subpath) or any(submatch.files())):
2707 sub = ctx.sub(subpath)
2708 sub = ctx.sub(subpath)
2708 try:
2709 try:
2709 recurse = m.exact(subpath) or subrepos
2710 recurse = m.exact(subpath) or subrepos
2710 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2711 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2711 ret = 0
2712 ret = 0
2712 except error.LookupError:
2713 except error.LookupError:
2713 ui.status(_("skipping missing subrepository: %s\n")
2714 ui.status(_("skipping missing subrepository: %s\n")
2714 % m.abs(subpath))
2715 % m.abs(subpath))
2715
2716
2716 return ret
2717 return ret
2717
2718
2718 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2719 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2719 join = lambda f: os.path.join(prefix, f)
2720 join = lambda f: os.path.join(prefix, f)
2720 ret = 0
2721 ret = 0
2721 s = repo.status(match=m, clean=True)
2722 s = repo.status(match=m, clean=True)
2722 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2723 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2723
2724
2724 wctx = repo[None]
2725 wctx = repo[None]
2725
2726
2726 if warnings is None:
2727 if warnings is None:
2727 warnings = []
2728 warnings = []
2728 warn = True
2729 warn = True
2729 else:
2730 else:
2730 warn = False
2731 warn = False
2731
2732
2732 subs = sorted(wctx.substate)
2733 subs = sorted(wctx.substate)
2733 total = len(subs)
2734 total = len(subs)
2734 count = 0
2735 count = 0
2735 for subpath in subs:
2736 for subpath in subs:
2736 count += 1
2737 count += 1
2737 submatch = matchmod.subdirmatcher(subpath, m)
2738 submatch = matchmod.subdirmatcher(subpath, m)
2738 if subrepos or m.exact(subpath) or any(submatch.files()):
2739 if subrepos or m.exact(subpath) or any(submatch.files()):
2739 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2740 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2740 sub = wctx.sub(subpath)
2741 sub = wctx.sub(subpath)
2741 try:
2742 try:
2742 if sub.removefiles(submatch, prefix, after, force, subrepos,
2743 if sub.removefiles(submatch, prefix, after, force, subrepos,
2743 warnings):
2744 warnings):
2744 ret = 1
2745 ret = 1
2745 except error.LookupError:
2746 except error.LookupError:
2746 warnings.append(_("skipping missing subrepository: %s\n")
2747 warnings.append(_("skipping missing subrepository: %s\n")
2747 % join(subpath))
2748 % join(subpath))
2748 ui.progress(_('searching'), None)
2749 ui.progress(_('searching'), None)
2749
2750
2750 # warn about failure to delete explicit files/dirs
2751 # warn about failure to delete explicit files/dirs
2751 deleteddirs = util.dirs(deleted)
2752 deleteddirs = util.dirs(deleted)
2752 files = m.files()
2753 files = m.files()
2753 total = len(files)
2754 total = len(files)
2754 count = 0
2755 count = 0
2755 for f in files:
2756 for f in files:
2756 def insubrepo():
2757 def insubrepo():
2757 for subpath in wctx.substate:
2758 for subpath in wctx.substate:
2758 if f.startswith(subpath + '/'):
2759 if f.startswith(subpath + '/'):
2759 return True
2760 return True
2760 return False
2761 return False
2761
2762
2762 count += 1
2763 count += 1
2763 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2764 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2764 isdir = f in deleteddirs or wctx.hasdir(f)
2765 isdir = f in deleteddirs or wctx.hasdir(f)
2765 if (f in repo.dirstate or isdir or f == '.'
2766 if (f in repo.dirstate or isdir or f == '.'
2766 or insubrepo() or f in subs):
2767 or insubrepo() or f in subs):
2767 continue
2768 continue
2768
2769
2769 if repo.wvfs.exists(f):
2770 if repo.wvfs.exists(f):
2770 if repo.wvfs.isdir(f):
2771 if repo.wvfs.isdir(f):
2771 warnings.append(_('not removing %s: no tracked files\n')
2772 warnings.append(_('not removing %s: no tracked files\n')
2772 % m.rel(f))
2773 % m.rel(f))
2773 else:
2774 else:
2774 warnings.append(_('not removing %s: file is untracked\n')
2775 warnings.append(_('not removing %s: file is untracked\n')
2775 % m.rel(f))
2776 % m.rel(f))
2776 # missing files will generate a warning elsewhere
2777 # missing files will generate a warning elsewhere
2777 ret = 1
2778 ret = 1
2778 ui.progress(_('deleting'), None)
2779 ui.progress(_('deleting'), None)
2779
2780
2780 if force:
2781 if force:
2781 list = modified + deleted + clean + added
2782 list = modified + deleted + clean + added
2782 elif after:
2783 elif after:
2783 list = deleted
2784 list = deleted
2784 remaining = modified + added + clean
2785 remaining = modified + added + clean
2785 total = len(remaining)
2786 total = len(remaining)
2786 count = 0
2787 count = 0
2787 for f in remaining:
2788 for f in remaining:
2788 count += 1
2789 count += 1
2789 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2790 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2790 warnings.append(_('not removing %s: file still exists\n')
2791 warnings.append(_('not removing %s: file still exists\n')
2791 % m.rel(f))
2792 % m.rel(f))
2792 ret = 1
2793 ret = 1
2793 ui.progress(_('skipping'), None)
2794 ui.progress(_('skipping'), None)
2794 else:
2795 else:
2795 list = deleted + clean
2796 list = deleted + clean
2796 total = len(modified) + len(added)
2797 total = len(modified) + len(added)
2797 count = 0
2798 count = 0
2798 for f in modified:
2799 for f in modified:
2799 count += 1
2800 count += 1
2800 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2801 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2801 warnings.append(_('not removing %s: file is modified (use -f'
2802 warnings.append(_('not removing %s: file is modified (use -f'
2802 ' to force removal)\n') % m.rel(f))
2803 ' to force removal)\n') % m.rel(f))
2803 ret = 1
2804 ret = 1
2804 for f in added:
2805 for f in added:
2805 count += 1
2806 count += 1
2806 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2807 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2807 warnings.append(_("not removing %s: file has been marked for add"
2808 warnings.append(_("not removing %s: file has been marked for add"
2808 " (use 'hg forget' to undo add)\n") % m.rel(f))
2809 " (use 'hg forget' to undo add)\n") % m.rel(f))
2809 ret = 1
2810 ret = 1
2810 ui.progress(_('skipping'), None)
2811 ui.progress(_('skipping'), None)
2811
2812
2812 list = sorted(list)
2813 list = sorted(list)
2813 total = len(list)
2814 total = len(list)
2814 count = 0
2815 count = 0
2815 for f in list:
2816 for f in list:
2816 count += 1
2817 count += 1
2817 if ui.verbose or not m.exact(f):
2818 if ui.verbose or not m.exact(f):
2818 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2819 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2819 ui.status(_('removing %s\n') % m.rel(f))
2820 ui.status(_('removing %s\n') % m.rel(f))
2820 ui.progress(_('deleting'), None)
2821 ui.progress(_('deleting'), None)
2821
2822
2822 with repo.wlock():
2823 with repo.wlock():
2823 if not after:
2824 if not after:
2824 for f in list:
2825 for f in list:
2825 if f in added:
2826 if f in added:
2826 continue # we never unlink added files on remove
2827 continue # we never unlink added files on remove
2827 repo.wvfs.unlinkpath(f, ignoremissing=True)
2828 repo.wvfs.unlinkpath(f, ignoremissing=True)
2828 repo[None].forget(list)
2829 repo[None].forget(list)
2829
2830
2830 if warn:
2831 if warn:
2831 for warning in warnings:
2832 for warning in warnings:
2832 ui.warn(warning)
2833 ui.warn(warning)
2833
2834
2834 return ret
2835 return ret
2835
2836
2836 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2837 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2837 err = 1
2838 err = 1
2838
2839
2839 def write(path):
2840 def write(path):
2840 filename = None
2841 filename = None
2841 if fntemplate:
2842 if fntemplate:
2842 filename = makefilename(repo, fntemplate, ctx.node(),
2843 filename = makefilename(repo, fntemplate, ctx.node(),
2843 pathname=os.path.join(prefix, path))
2844 pathname=os.path.join(prefix, path))
2844 with formatter.maybereopen(basefm, filename, opts) as fm:
2845 with formatter.maybereopen(basefm, filename, opts) as fm:
2845 data = ctx[path].data()
2846 data = ctx[path].data()
2846 if opts.get('decode'):
2847 if opts.get('decode'):
2847 data = repo.wwritedata(path, data)
2848 data = repo.wwritedata(path, data)
2848 fm.startitem()
2849 fm.startitem()
2849 fm.write('data', '%s', data)
2850 fm.write('data', '%s', data)
2850 fm.data(abspath=path, path=matcher.rel(path))
2851 fm.data(abspath=path, path=matcher.rel(path))
2851
2852
2852 # Automation often uses hg cat on single files, so special case it
2853 # Automation often uses hg cat on single files, so special case it
2853 # for performance to avoid the cost of parsing the manifest.
2854 # for performance to avoid the cost of parsing the manifest.
2854 if len(matcher.files()) == 1 and not matcher.anypats():
2855 if len(matcher.files()) == 1 and not matcher.anypats():
2855 file = matcher.files()[0]
2856 file = matcher.files()[0]
2856 mfl = repo.manifestlog
2857 mfl = repo.manifestlog
2857 mfnode = ctx.manifestnode()
2858 mfnode = ctx.manifestnode()
2858 try:
2859 try:
2859 if mfnode and mfl[mfnode].find(file)[0]:
2860 if mfnode and mfl[mfnode].find(file)[0]:
2860 write(file)
2861 write(file)
2861 return 0
2862 return 0
2862 except KeyError:
2863 except KeyError:
2863 pass
2864 pass
2864
2865
2865 for abs in ctx.walk(matcher):
2866 for abs in ctx.walk(matcher):
2866 write(abs)
2867 write(abs)
2867 err = 0
2868 err = 0
2868
2869
2869 for subpath in sorted(ctx.substate):
2870 for subpath in sorted(ctx.substate):
2870 sub = ctx.sub(subpath)
2871 sub = ctx.sub(subpath)
2871 try:
2872 try:
2872 submatch = matchmod.subdirmatcher(subpath, matcher)
2873 submatch = matchmod.subdirmatcher(subpath, matcher)
2873
2874
2874 if not sub.cat(submatch, basefm, fntemplate,
2875 if not sub.cat(submatch, basefm, fntemplate,
2875 os.path.join(prefix, sub._path), **opts):
2876 os.path.join(prefix, sub._path), **opts):
2876 err = 0
2877 err = 0
2877 except error.RepoLookupError:
2878 except error.RepoLookupError:
2878 ui.status(_("skipping missing subrepository: %s\n")
2879 ui.status(_("skipping missing subrepository: %s\n")
2879 % os.path.join(prefix, subpath))
2880 % os.path.join(prefix, subpath))
2880
2881
2881 return err
2882 return err
2882
2883
2883 def commit(ui, repo, commitfunc, pats, opts):
2884 def commit(ui, repo, commitfunc, pats, opts):
2884 '''commit the specified files or all outstanding changes'''
2885 '''commit the specified files or all outstanding changes'''
2885 date = opts.get('date')
2886 date = opts.get('date')
2886 if date:
2887 if date:
2887 opts['date'] = util.parsedate(date)
2888 opts['date'] = util.parsedate(date)
2888 message = logmessage(ui, opts)
2889 message = logmessage(ui, opts)
2889 matcher = scmutil.match(repo[None], pats, opts)
2890 matcher = scmutil.match(repo[None], pats, opts)
2890
2891
2892 dsguard = None
2891 # extract addremove carefully -- this function can be called from a command
2893 # extract addremove carefully -- this function can be called from a command
2892 # that doesn't support addremove
2894 # that doesn't support addremove
2893 if opts.get('addremove'):
2895 try:
2894 if scmutil.addremove(repo, matcher, "", opts) != 0:
2896 if opts.get('addremove'):
2895 raise error.Abort(
2897 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2896 _("failed to mark all new/missing files as added/removed"))
2898 if scmutil.addremove(repo, matcher, "", opts) != 0:
2897
2899 raise error.Abort(
2898 return commitfunc(ui, repo, message, matcher, opts)
2900 _("failed to mark all new/missing files as added/removed"))
2901
2902 r = commitfunc(ui, repo, message, matcher, opts)
2903 if dsguard:
2904 dsguard.close()
2905 return r
2906 finally:
2907 if dsguard:
2908 dsguard.release()
2899
2909
2900 def samefile(f, ctx1, ctx2):
2910 def samefile(f, ctx1, ctx2):
2901 if f in ctx1.manifest():
2911 if f in ctx1.manifest():
2902 a = ctx1.filectx(f)
2912 a = ctx1.filectx(f)
2903 if f in ctx2.manifest():
2913 if f in ctx2.manifest():
2904 b = ctx2.filectx(f)
2914 b = ctx2.filectx(f)
2905 return (not a.cmp(b)
2915 return (not a.cmp(b)
2906 and a.flags() == b.flags())
2916 and a.flags() == b.flags())
2907 else:
2917 else:
2908 return False
2918 return False
2909 else:
2919 else:
2910 return f not in ctx2.manifest()
2920 return f not in ctx2.manifest()
2911
2921
2912 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2922 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2913 # avoid cycle context -> subrepo -> cmdutil
2923 # avoid cycle context -> subrepo -> cmdutil
2914 from . import context
2924 from . import context
2915
2925
2916 # amend will reuse the existing user if not specified, but the obsolete
2926 # amend will reuse the existing user if not specified, but the obsolete
2917 # marker creation requires that the current user's name is specified.
2927 # marker creation requires that the current user's name is specified.
2918 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2928 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2919 ui.username() # raise exception if username not set
2929 ui.username() # raise exception if username not set
2920
2930
2921 ui.note(_('amending changeset %s\n') % old)
2931 ui.note(_('amending changeset %s\n') % old)
2922 base = old.p1()
2932 base = old.p1()
2923
2933
2924 newid = None
2934 newid = None
2925 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2935 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2926 # See if we got a message from -m or -l, if not, open the editor
2936 # See if we got a message from -m or -l, if not, open the editor
2927 # with the message of the changeset to amend
2937 # with the message of the changeset to amend
2928 message = logmessage(ui, opts)
2938 message = logmessage(ui, opts)
2929 # ensure logfile does not conflict with later enforcement of the
2939 # ensure logfile does not conflict with later enforcement of the
2930 # message. potential logfile content has been processed by
2940 # message. potential logfile content has been processed by
2931 # `logmessage` anyway.
2941 # `logmessage` anyway.
2932 opts.pop('logfile')
2942 opts.pop('logfile')
2933 # First, do a regular commit to record all changes in the working
2943 # First, do a regular commit to record all changes in the working
2934 # directory (if there are any)
2944 # directory (if there are any)
2935 ui.callhooks = False
2945 ui.callhooks = False
2936 activebookmark = repo._bookmarks.active
2946 activebookmark = repo._bookmarks.active
2937 try:
2947 try:
2938 repo._bookmarks.active = None
2948 repo._bookmarks.active = None
2939 opts['message'] = 'temporary amend commit for %s' % old
2949 opts['message'] = 'temporary amend commit for %s' % old
2940 node = commit(ui, repo, commitfunc, pats, opts)
2950 node = commit(ui, repo, commitfunc, pats, opts)
2941 finally:
2951 finally:
2942 repo._bookmarks.active = activebookmark
2952 repo._bookmarks.active = activebookmark
2943 ui.callhooks = True
2953 ui.callhooks = True
2944 ctx = repo[node]
2954 ctx = repo[node]
2945
2955
2946 # Participating changesets:
2956 # Participating changesets:
2947 #
2957 #
2948 # node/ctx o - new (intermediate) commit that contains changes
2958 # node/ctx o - new (intermediate) commit that contains changes
2949 # | from working dir to go into amending commit
2959 # | from working dir to go into amending commit
2950 # | (or a workingctx if there were no changes)
2960 # | (or a workingctx if there were no changes)
2951 # |
2961 # |
2952 # old o - changeset to amend
2962 # old o - changeset to amend
2953 # |
2963 # |
2954 # base o - parent of amending changeset
2964 # base o - parent of amending changeset
2955
2965
2956 # Update extra dict from amended commit (e.g. to preserve graft
2966 # Update extra dict from amended commit (e.g. to preserve graft
2957 # source)
2967 # source)
2958 extra.update(old.extra())
2968 extra.update(old.extra())
2959
2969
2960 # Also update it from the intermediate commit or from the wctx
2970 # Also update it from the intermediate commit or from the wctx
2961 extra.update(ctx.extra())
2971 extra.update(ctx.extra())
2962
2972
2963 if len(old.parents()) > 1:
2973 if len(old.parents()) > 1:
2964 # ctx.files() isn't reliable for merges, so fall back to the
2974 # ctx.files() isn't reliable for merges, so fall back to the
2965 # slower repo.status() method
2975 # slower repo.status() method
2966 files = set([fn for st in repo.status(base, old)[:3]
2976 files = set([fn for st in repo.status(base, old)[:3]
2967 for fn in st])
2977 for fn in st])
2968 else:
2978 else:
2969 files = set(old.files())
2979 files = set(old.files())
2970
2980
2971 # Second, we use either the commit we just did, or if there were no
2981 # Second, we use either the commit we just did, or if there were no
2972 # changes the parent of the working directory as the version of the
2982 # changes the parent of the working directory as the version of the
2973 # files in the final amend commit
2983 # files in the final amend commit
2974 if node:
2984 if node:
2975 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2985 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2976
2986
2977 user = ctx.user()
2987 user = ctx.user()
2978 date = ctx.date()
2988 date = ctx.date()
2979 # Recompute copies (avoid recording a -> b -> a)
2989 # Recompute copies (avoid recording a -> b -> a)
2980 copied = copies.pathcopies(base, ctx)
2990 copied = copies.pathcopies(base, ctx)
2981 if old.p2:
2991 if old.p2:
2982 copied.update(copies.pathcopies(old.p2(), ctx))
2992 copied.update(copies.pathcopies(old.p2(), ctx))
2983
2993
2984 # Prune files which were reverted by the updates: if old
2994 # Prune files which were reverted by the updates: if old
2985 # introduced file X and our intermediate commit, node,
2995 # introduced file X and our intermediate commit, node,
2986 # renamed that file, then those two files are the same and
2996 # renamed that file, then those two files are the same and
2987 # we can discard X from our list of files. Likewise if X
2997 # we can discard X from our list of files. Likewise if X
2988 # was deleted, it's no longer relevant
2998 # was deleted, it's no longer relevant
2989 files.update(ctx.files())
2999 files.update(ctx.files())
2990 files = [f for f in files if not samefile(f, ctx, base)]
3000 files = [f for f in files if not samefile(f, ctx, base)]
2991
3001
2992 def filectxfn(repo, ctx_, path):
3002 def filectxfn(repo, ctx_, path):
2993 try:
3003 try:
2994 fctx = ctx[path]
3004 fctx = ctx[path]
2995 flags = fctx.flags()
3005 flags = fctx.flags()
2996 mctx = context.memfilectx(repo,
3006 mctx = context.memfilectx(repo,
2997 fctx.path(), fctx.data(),
3007 fctx.path(), fctx.data(),
2998 islink='l' in flags,
3008 islink='l' in flags,
2999 isexec='x' in flags,
3009 isexec='x' in flags,
3000 copied=copied.get(path))
3010 copied=copied.get(path))
3001 return mctx
3011 return mctx
3002 except KeyError:
3012 except KeyError:
3003 return None
3013 return None
3004 else:
3014 else:
3005 ui.note(_('copying changeset %s to %s\n') % (old, base))
3015 ui.note(_('copying changeset %s to %s\n') % (old, base))
3006
3016
3007 # Use version of files as in the old cset
3017 # Use version of files as in the old cset
3008 def filectxfn(repo, ctx_, path):
3018 def filectxfn(repo, ctx_, path):
3009 try:
3019 try:
3010 return old.filectx(path)
3020 return old.filectx(path)
3011 except KeyError:
3021 except KeyError:
3012 return None
3022 return None
3013
3023
3014 user = opts.get('user') or old.user()
3024 user = opts.get('user') or old.user()
3015 date = opts.get('date') or old.date()
3025 date = opts.get('date') or old.date()
3016 editform = mergeeditform(old, 'commit.amend')
3026 editform = mergeeditform(old, 'commit.amend')
3017 editor = getcommiteditor(editform=editform,
3027 editor = getcommiteditor(editform=editform,
3018 **pycompat.strkwargs(opts))
3028 **pycompat.strkwargs(opts))
3019 if not message:
3029 if not message:
3020 editor = getcommiteditor(edit=True, editform=editform)
3030 editor = getcommiteditor(edit=True, editform=editform)
3021 message = old.description()
3031 message = old.description()
3022
3032
3023 pureextra = extra.copy()
3033 pureextra = extra.copy()
3024 extra['amend_source'] = old.hex()
3034 extra['amend_source'] = old.hex()
3025
3035
3026 new = context.memctx(repo,
3036 new = context.memctx(repo,
3027 parents=[base.node(), old.p2().node()],
3037 parents=[base.node(), old.p2().node()],
3028 text=message,
3038 text=message,
3029 files=files,
3039 files=files,
3030 filectxfn=filectxfn,
3040 filectxfn=filectxfn,
3031 user=user,
3041 user=user,
3032 date=date,
3042 date=date,
3033 extra=extra,
3043 extra=extra,
3034 editor=editor)
3044 editor=editor)
3035
3045
3036 newdesc = changelog.stripdesc(new.description())
3046 newdesc = changelog.stripdesc(new.description())
3037 if ((not node)
3047 if ((not node)
3038 and newdesc == old.description()
3048 and newdesc == old.description()
3039 and user == old.user()
3049 and user == old.user()
3040 and date == old.date()
3050 and date == old.date()
3041 and pureextra == old.extra()):
3051 and pureextra == old.extra()):
3042 # nothing changed. continuing here would create a new node
3052 # nothing changed. continuing here would create a new node
3043 # anyway because of the amend_source noise.
3053 # anyway because of the amend_source noise.
3044 #
3054 #
3045 # This not what we expect from amend.
3055 # This not what we expect from amend.
3046 return old.node()
3056 return old.node()
3047
3057
3048 ph = repo.ui.config('phases', 'new-commit', phases.draft)
3058 ph = repo.ui.config('phases', 'new-commit', phases.draft)
3049 try:
3059 try:
3050 if opts.get('secret'):
3060 if opts.get('secret'):
3051 commitphase = 'secret'
3061 commitphase = 'secret'
3052 else:
3062 else:
3053 commitphase = old.phase()
3063 commitphase = old.phase()
3054 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
3064 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
3055 newid = repo.commitctx(new)
3065 newid = repo.commitctx(new)
3056 finally:
3066 finally:
3057 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
3067 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
3058 if newid != old.node():
3068 if newid != old.node():
3059 # Reroute the working copy parent to the new changeset
3069 # Reroute the working copy parent to the new changeset
3060 repo.setparents(newid, nullid)
3070 repo.setparents(newid, nullid)
3061 mapping = {old.node(): (newid,)}
3071 mapping = {old.node(): (newid,)}
3062 if node:
3072 if node:
3063 mapping[node] = ()
3073 mapping[node] = ()
3064 scmutil.cleanupnodes(repo, mapping, 'amend')
3074 scmutil.cleanupnodes(repo, mapping, 'amend')
3065 return newid
3075 return newid
3066
3076
3067 def commiteditor(repo, ctx, subs, editform=''):
3077 def commiteditor(repo, ctx, subs, editform=''):
3068 if ctx.description():
3078 if ctx.description():
3069 return ctx.description()
3079 return ctx.description()
3070 return commitforceeditor(repo, ctx, subs, editform=editform,
3080 return commitforceeditor(repo, ctx, subs, editform=editform,
3071 unchangedmessagedetection=True)
3081 unchangedmessagedetection=True)
3072
3082
3073 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
3083 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
3074 editform='', unchangedmessagedetection=False):
3084 editform='', unchangedmessagedetection=False):
3075 if not extramsg:
3085 if not extramsg:
3076 extramsg = _("Leave message empty to abort commit.")
3086 extramsg = _("Leave message empty to abort commit.")
3077
3087
3078 forms = [e for e in editform.split('.') if e]
3088 forms = [e for e in editform.split('.') if e]
3079 forms.insert(0, 'changeset')
3089 forms.insert(0, 'changeset')
3080 templatetext = None
3090 templatetext = None
3081 while forms:
3091 while forms:
3082 ref = '.'.join(forms)
3092 ref = '.'.join(forms)
3083 if repo.ui.config('committemplate', ref):
3093 if repo.ui.config('committemplate', ref):
3084 templatetext = committext = buildcommittemplate(
3094 templatetext = committext = buildcommittemplate(
3085 repo, ctx, subs, extramsg, ref)
3095 repo, ctx, subs, extramsg, ref)
3086 break
3096 break
3087 forms.pop()
3097 forms.pop()
3088 else:
3098 else:
3089 committext = buildcommittext(repo, ctx, subs, extramsg)
3099 committext = buildcommittext(repo, ctx, subs, extramsg)
3090
3100
3091 # run editor in the repository root
3101 # run editor in the repository root
3092 olddir = pycompat.getcwd()
3102 olddir = pycompat.getcwd()
3093 os.chdir(repo.root)
3103 os.chdir(repo.root)
3094
3104
3095 # make in-memory changes visible to external process
3105 # make in-memory changes visible to external process
3096 tr = repo.currenttransaction()
3106 tr = repo.currenttransaction()
3097 repo.dirstate.write(tr)
3107 repo.dirstate.write(tr)
3098 pending = tr and tr.writepending() and repo.root
3108 pending = tr and tr.writepending() and repo.root
3099
3109
3100 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
3110 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
3101 editform=editform, pending=pending,
3111 editform=editform, pending=pending,
3102 repopath=repo.path)
3112 repopath=repo.path)
3103 text = editortext
3113 text = editortext
3104
3114
3105 # strip away anything below this special string (used for editors that want
3115 # strip away anything below this special string (used for editors that want
3106 # to display the diff)
3116 # to display the diff)
3107 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3117 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3108 if stripbelow:
3118 if stripbelow:
3109 text = text[:stripbelow.start()]
3119 text = text[:stripbelow.start()]
3110
3120
3111 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
3121 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
3112 os.chdir(olddir)
3122 os.chdir(olddir)
3113
3123
3114 if finishdesc:
3124 if finishdesc:
3115 text = finishdesc(text)
3125 text = finishdesc(text)
3116 if not text.strip():
3126 if not text.strip():
3117 raise error.Abort(_("empty commit message"))
3127 raise error.Abort(_("empty commit message"))
3118 if unchangedmessagedetection and editortext == templatetext:
3128 if unchangedmessagedetection and editortext == templatetext:
3119 raise error.Abort(_("commit message unchanged"))
3129 raise error.Abort(_("commit message unchanged"))
3120
3130
3121 return text
3131 return text
3122
3132
3123 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3133 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3124 ui = repo.ui
3134 ui = repo.ui
3125 spec = formatter.templatespec(ref, None, None)
3135 spec = formatter.templatespec(ref, None, None)
3126 t = changeset_templater(ui, repo, spec, None, {}, False)
3136 t = changeset_templater(ui, repo, spec, None, {}, False)
3127 t.t.cache.update((k, templater.unquotestring(v))
3137 t.t.cache.update((k, templater.unquotestring(v))
3128 for k, v in repo.ui.configitems('committemplate'))
3138 for k, v in repo.ui.configitems('committemplate'))
3129
3139
3130 if not extramsg:
3140 if not extramsg:
3131 extramsg = '' # ensure that extramsg is string
3141 extramsg = '' # ensure that extramsg is string
3132
3142
3133 ui.pushbuffer()
3143 ui.pushbuffer()
3134 t.show(ctx, extramsg=extramsg)
3144 t.show(ctx, extramsg=extramsg)
3135 return ui.popbuffer()
3145 return ui.popbuffer()
3136
3146
3137 def hgprefix(msg):
3147 def hgprefix(msg):
3138 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
3148 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
3139
3149
3140 def buildcommittext(repo, ctx, subs, extramsg):
3150 def buildcommittext(repo, ctx, subs, extramsg):
3141 edittext = []
3151 edittext = []
3142 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3152 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3143 if ctx.description():
3153 if ctx.description():
3144 edittext.append(ctx.description())
3154 edittext.append(ctx.description())
3145 edittext.append("")
3155 edittext.append("")
3146 edittext.append("") # Empty line between message and comments.
3156 edittext.append("") # Empty line between message and comments.
3147 edittext.append(hgprefix(_("Enter commit message."
3157 edittext.append(hgprefix(_("Enter commit message."
3148 " Lines beginning with 'HG:' are removed.")))
3158 " Lines beginning with 'HG:' are removed.")))
3149 edittext.append(hgprefix(extramsg))
3159 edittext.append(hgprefix(extramsg))
3150 edittext.append("HG: --")
3160 edittext.append("HG: --")
3151 edittext.append(hgprefix(_("user: %s") % ctx.user()))
3161 edittext.append(hgprefix(_("user: %s") % ctx.user()))
3152 if ctx.p2():
3162 if ctx.p2():
3153 edittext.append(hgprefix(_("branch merge")))
3163 edittext.append(hgprefix(_("branch merge")))
3154 if ctx.branch():
3164 if ctx.branch():
3155 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
3165 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
3156 if bookmarks.isactivewdirparent(repo):
3166 if bookmarks.isactivewdirparent(repo):
3157 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
3167 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
3158 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
3168 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
3159 edittext.extend([hgprefix(_("added %s") % f) for f in added])
3169 edittext.extend([hgprefix(_("added %s") % f) for f in added])
3160 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
3170 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
3161 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
3171 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
3162 if not added and not modified and not removed:
3172 if not added and not modified and not removed:
3163 edittext.append(hgprefix(_("no files changed")))
3173 edittext.append(hgprefix(_("no files changed")))
3164 edittext.append("")
3174 edittext.append("")
3165
3175
3166 return "\n".join(edittext)
3176 return "\n".join(edittext)
3167
3177
3168 def commitstatus(repo, node, branch, bheads=None, opts=None):
3178 def commitstatus(repo, node, branch, bheads=None, opts=None):
3169 if opts is None:
3179 if opts is None:
3170 opts = {}
3180 opts = {}
3171 ctx = repo[node]
3181 ctx = repo[node]
3172 parents = ctx.parents()
3182 parents = ctx.parents()
3173
3183
3174 if (not opts.get('amend') and bheads and node not in bheads and not
3184 if (not opts.get('amend') and bheads and node not in bheads and not
3175 [x for x in parents if x.node() in bheads and x.branch() == branch]):
3185 [x for x in parents if x.node() in bheads and x.branch() == branch]):
3176 repo.ui.status(_('created new head\n'))
3186 repo.ui.status(_('created new head\n'))
3177 # The message is not printed for initial roots. For the other
3187 # The message is not printed for initial roots. For the other
3178 # changesets, it is printed in the following situations:
3188 # changesets, it is printed in the following situations:
3179 #
3189 #
3180 # Par column: for the 2 parents with ...
3190 # Par column: for the 2 parents with ...
3181 # N: null or no parent
3191 # N: null or no parent
3182 # B: parent is on another named branch
3192 # B: parent is on another named branch
3183 # C: parent is a regular non head changeset
3193 # C: parent is a regular non head changeset
3184 # H: parent was a branch head of the current branch
3194 # H: parent was a branch head of the current branch
3185 # Msg column: whether we print "created new head" message
3195 # Msg column: whether we print "created new head" message
3186 # In the following, it is assumed that there already exists some
3196 # In the following, it is assumed that there already exists some
3187 # initial branch heads of the current branch, otherwise nothing is
3197 # initial branch heads of the current branch, otherwise nothing is
3188 # printed anyway.
3198 # printed anyway.
3189 #
3199 #
3190 # Par Msg Comment
3200 # Par Msg Comment
3191 # N N y additional topo root
3201 # N N y additional topo root
3192 #
3202 #
3193 # B N y additional branch root
3203 # B N y additional branch root
3194 # C N y additional topo head
3204 # C N y additional topo head
3195 # H N n usual case
3205 # H N n usual case
3196 #
3206 #
3197 # B B y weird additional branch root
3207 # B B y weird additional branch root
3198 # C B y branch merge
3208 # C B y branch merge
3199 # H B n merge with named branch
3209 # H B n merge with named branch
3200 #
3210 #
3201 # C C y additional head from merge
3211 # C C y additional head from merge
3202 # C H n merge with a head
3212 # C H n merge with a head
3203 #
3213 #
3204 # H H n head merge: head count decreases
3214 # H H n head merge: head count decreases
3205
3215
3206 if not opts.get('close_branch'):
3216 if not opts.get('close_branch'):
3207 for r in parents:
3217 for r in parents:
3208 if r.closesbranch() and r.branch() == branch:
3218 if r.closesbranch() and r.branch() == branch:
3209 repo.ui.status(_('reopening closed branch head %d\n') % r)
3219 repo.ui.status(_('reopening closed branch head %d\n') % r)
3210
3220
3211 if repo.ui.debugflag:
3221 if repo.ui.debugflag:
3212 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
3222 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
3213 elif repo.ui.verbose:
3223 elif repo.ui.verbose:
3214 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
3224 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
3215
3225
3216 def postcommitstatus(repo, pats, opts):
3226 def postcommitstatus(repo, pats, opts):
3217 return repo.status(match=scmutil.match(repo[None], pats, opts))
3227 return repo.status(match=scmutil.match(repo[None], pats, opts))
3218
3228
3219 def revert(ui, repo, ctx, parents, *pats, **opts):
3229 def revert(ui, repo, ctx, parents, *pats, **opts):
3220 parent, p2 = parents
3230 parent, p2 = parents
3221 node = ctx.node()
3231 node = ctx.node()
3222
3232
3223 mf = ctx.manifest()
3233 mf = ctx.manifest()
3224 if node == p2:
3234 if node == p2:
3225 parent = p2
3235 parent = p2
3226
3236
3227 # need all matching names in dirstate and manifest of target rev,
3237 # need all matching names in dirstate and manifest of target rev,
3228 # so have to walk both. do not print errors if files exist in one
3238 # so have to walk both. do not print errors if files exist in one
3229 # but not other. in both cases, filesets should be evaluated against
3239 # but not other. in both cases, filesets should be evaluated against
3230 # workingctx to get consistent result (issue4497). this means 'set:**'
3240 # workingctx to get consistent result (issue4497). this means 'set:**'
3231 # cannot be used to select missing files from target rev.
3241 # cannot be used to select missing files from target rev.
3232
3242
3233 # `names` is a mapping for all elements in working copy and target revision
3243 # `names` is a mapping for all elements in working copy and target revision
3234 # The mapping is in the form:
3244 # The mapping is in the form:
3235 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3245 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3236 names = {}
3246 names = {}
3237
3247
3238 with repo.wlock():
3248 with repo.wlock():
3239 ## filling of the `names` mapping
3249 ## filling of the `names` mapping
3240 # walk dirstate to fill `names`
3250 # walk dirstate to fill `names`
3241
3251
3242 interactive = opts.get('interactive', False)
3252 interactive = opts.get('interactive', False)
3243 wctx = repo[None]
3253 wctx = repo[None]
3244 m = scmutil.match(wctx, pats, opts)
3254 m = scmutil.match(wctx, pats, opts)
3245
3255
3246 # we'll need this later
3256 # we'll need this later
3247 targetsubs = sorted(s for s in wctx.substate if m(s))
3257 targetsubs = sorted(s for s in wctx.substate if m(s))
3248
3258
3249 if not m.always():
3259 if not m.always():
3250 matcher = matchmod.badmatch(m, lambda x, y: False)
3260 matcher = matchmod.badmatch(m, lambda x, y: False)
3251 for abs in wctx.walk(matcher):
3261 for abs in wctx.walk(matcher):
3252 names[abs] = m.rel(abs), m.exact(abs)
3262 names[abs] = m.rel(abs), m.exact(abs)
3253
3263
3254 # walk target manifest to fill `names`
3264 # walk target manifest to fill `names`
3255
3265
3256 def badfn(path, msg):
3266 def badfn(path, msg):
3257 if path in names:
3267 if path in names:
3258 return
3268 return
3259 if path in ctx.substate:
3269 if path in ctx.substate:
3260 return
3270 return
3261 path_ = path + '/'
3271 path_ = path + '/'
3262 for f in names:
3272 for f in names:
3263 if f.startswith(path_):
3273 if f.startswith(path_):
3264 return
3274 return
3265 ui.warn("%s: %s\n" % (m.rel(path), msg))
3275 ui.warn("%s: %s\n" % (m.rel(path), msg))
3266
3276
3267 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3277 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3268 if abs not in names:
3278 if abs not in names:
3269 names[abs] = m.rel(abs), m.exact(abs)
3279 names[abs] = m.rel(abs), m.exact(abs)
3270
3280
3271 # Find status of all file in `names`.
3281 # Find status of all file in `names`.
3272 m = scmutil.matchfiles(repo, names)
3282 m = scmutil.matchfiles(repo, names)
3273
3283
3274 changes = repo.status(node1=node, match=m,
3284 changes = repo.status(node1=node, match=m,
3275 unknown=True, ignored=True, clean=True)
3285 unknown=True, ignored=True, clean=True)
3276 else:
3286 else:
3277 changes = repo.status(node1=node, match=m)
3287 changes = repo.status(node1=node, match=m)
3278 for kind in changes:
3288 for kind in changes:
3279 for abs in kind:
3289 for abs in kind:
3280 names[abs] = m.rel(abs), m.exact(abs)
3290 names[abs] = m.rel(abs), m.exact(abs)
3281
3291
3282 m = scmutil.matchfiles(repo, names)
3292 m = scmutil.matchfiles(repo, names)
3283
3293
3284 modified = set(changes.modified)
3294 modified = set(changes.modified)
3285 added = set(changes.added)
3295 added = set(changes.added)
3286 removed = set(changes.removed)
3296 removed = set(changes.removed)
3287 _deleted = set(changes.deleted)
3297 _deleted = set(changes.deleted)
3288 unknown = set(changes.unknown)
3298 unknown = set(changes.unknown)
3289 unknown.update(changes.ignored)
3299 unknown.update(changes.ignored)
3290 clean = set(changes.clean)
3300 clean = set(changes.clean)
3291 modadded = set()
3301 modadded = set()
3292
3302
3293 # We need to account for the state of the file in the dirstate,
3303 # We need to account for the state of the file in the dirstate,
3294 # even when we revert against something else than parent. This will
3304 # even when we revert against something else than parent. This will
3295 # slightly alter the behavior of revert (doing back up or not, delete
3305 # slightly alter the behavior of revert (doing back up or not, delete
3296 # or just forget etc).
3306 # or just forget etc).
3297 if parent == node:
3307 if parent == node:
3298 dsmodified = modified
3308 dsmodified = modified
3299 dsadded = added
3309 dsadded = added
3300 dsremoved = removed
3310 dsremoved = removed
3301 # store all local modifications, useful later for rename detection
3311 # store all local modifications, useful later for rename detection
3302 localchanges = dsmodified | dsadded
3312 localchanges = dsmodified | dsadded
3303 modified, added, removed = set(), set(), set()
3313 modified, added, removed = set(), set(), set()
3304 else:
3314 else:
3305 changes = repo.status(node1=parent, match=m)
3315 changes = repo.status(node1=parent, match=m)
3306 dsmodified = set(changes.modified)
3316 dsmodified = set(changes.modified)
3307 dsadded = set(changes.added)
3317 dsadded = set(changes.added)
3308 dsremoved = set(changes.removed)
3318 dsremoved = set(changes.removed)
3309 # store all local modifications, useful later for rename detection
3319 # store all local modifications, useful later for rename detection
3310 localchanges = dsmodified | dsadded
3320 localchanges = dsmodified | dsadded
3311
3321
3312 # only take into account for removes between wc and target
3322 # only take into account for removes between wc and target
3313 clean |= dsremoved - removed
3323 clean |= dsremoved - removed
3314 dsremoved &= removed
3324 dsremoved &= removed
3315 # distinct between dirstate remove and other
3325 # distinct between dirstate remove and other
3316 removed -= dsremoved
3326 removed -= dsremoved
3317
3327
3318 modadded = added & dsmodified
3328 modadded = added & dsmodified
3319 added -= modadded
3329 added -= modadded
3320
3330
3321 # tell newly modified apart.
3331 # tell newly modified apart.
3322 dsmodified &= modified
3332 dsmodified &= modified
3323 dsmodified |= modified & dsadded # dirstate added may need backup
3333 dsmodified |= modified & dsadded # dirstate added may need backup
3324 modified -= dsmodified
3334 modified -= dsmodified
3325
3335
3326 # We need to wait for some post-processing to update this set
3336 # We need to wait for some post-processing to update this set
3327 # before making the distinction. The dirstate will be used for
3337 # before making the distinction. The dirstate will be used for
3328 # that purpose.
3338 # that purpose.
3329 dsadded = added
3339 dsadded = added
3330
3340
3331 # in case of merge, files that are actually added can be reported as
3341 # in case of merge, files that are actually added can be reported as
3332 # modified, we need to post process the result
3342 # modified, we need to post process the result
3333 if p2 != nullid:
3343 if p2 != nullid:
3334 mergeadd = set(dsmodified)
3344 mergeadd = set(dsmodified)
3335 for path in dsmodified:
3345 for path in dsmodified:
3336 if path in mf:
3346 if path in mf:
3337 mergeadd.remove(path)
3347 mergeadd.remove(path)
3338 dsadded |= mergeadd
3348 dsadded |= mergeadd
3339 dsmodified -= mergeadd
3349 dsmodified -= mergeadd
3340
3350
3341 # if f is a rename, update `names` to also revert the source
3351 # if f is a rename, update `names` to also revert the source
3342 cwd = repo.getcwd()
3352 cwd = repo.getcwd()
3343 for f in localchanges:
3353 for f in localchanges:
3344 src = repo.dirstate.copied(f)
3354 src = repo.dirstate.copied(f)
3345 # XXX should we check for rename down to target node?
3355 # XXX should we check for rename down to target node?
3346 if src and src not in names and repo.dirstate[src] == 'r':
3356 if src and src not in names and repo.dirstate[src] == 'r':
3347 dsremoved.add(src)
3357 dsremoved.add(src)
3348 names[src] = (repo.pathto(src, cwd), True)
3358 names[src] = (repo.pathto(src, cwd), True)
3349
3359
3350 # determine the exact nature of the deleted changesets
3360 # determine the exact nature of the deleted changesets
3351 deladded = set(_deleted)
3361 deladded = set(_deleted)
3352 for path in _deleted:
3362 for path in _deleted:
3353 if path in mf:
3363 if path in mf:
3354 deladded.remove(path)
3364 deladded.remove(path)
3355 deleted = _deleted - deladded
3365 deleted = _deleted - deladded
3356
3366
3357 # distinguish between file to forget and the other
3367 # distinguish between file to forget and the other
3358 added = set()
3368 added = set()
3359 for abs in dsadded:
3369 for abs in dsadded:
3360 if repo.dirstate[abs] != 'a':
3370 if repo.dirstate[abs] != 'a':
3361 added.add(abs)
3371 added.add(abs)
3362 dsadded -= added
3372 dsadded -= added
3363
3373
3364 for abs in deladded:
3374 for abs in deladded:
3365 if repo.dirstate[abs] == 'a':
3375 if repo.dirstate[abs] == 'a':
3366 dsadded.add(abs)
3376 dsadded.add(abs)
3367 deladded -= dsadded
3377 deladded -= dsadded
3368
3378
3369 # For files marked as removed, we check if an unknown file is present at
3379 # For files marked as removed, we check if an unknown file is present at
3370 # the same path. If a such file exists it may need to be backed up.
3380 # the same path. If a such file exists it may need to be backed up.
3371 # Making the distinction at this stage helps have simpler backup
3381 # Making the distinction at this stage helps have simpler backup
3372 # logic.
3382 # logic.
3373 removunk = set()
3383 removunk = set()
3374 for abs in removed:
3384 for abs in removed:
3375 target = repo.wjoin(abs)
3385 target = repo.wjoin(abs)
3376 if os.path.lexists(target):
3386 if os.path.lexists(target):
3377 removunk.add(abs)
3387 removunk.add(abs)
3378 removed -= removunk
3388 removed -= removunk
3379
3389
3380 dsremovunk = set()
3390 dsremovunk = set()
3381 for abs in dsremoved:
3391 for abs in dsremoved:
3382 target = repo.wjoin(abs)
3392 target = repo.wjoin(abs)
3383 if os.path.lexists(target):
3393 if os.path.lexists(target):
3384 dsremovunk.add(abs)
3394 dsremovunk.add(abs)
3385 dsremoved -= dsremovunk
3395 dsremoved -= dsremovunk
3386
3396
3387 # action to be actually performed by revert
3397 # action to be actually performed by revert
3388 # (<list of file>, message>) tuple
3398 # (<list of file>, message>) tuple
3389 actions = {'revert': ([], _('reverting %s\n')),
3399 actions = {'revert': ([], _('reverting %s\n')),
3390 'add': ([], _('adding %s\n')),
3400 'add': ([], _('adding %s\n')),
3391 'remove': ([], _('removing %s\n')),
3401 'remove': ([], _('removing %s\n')),
3392 'drop': ([], _('removing %s\n')),
3402 'drop': ([], _('removing %s\n')),
3393 'forget': ([], _('forgetting %s\n')),
3403 'forget': ([], _('forgetting %s\n')),
3394 'undelete': ([], _('undeleting %s\n')),
3404 'undelete': ([], _('undeleting %s\n')),
3395 'noop': (None, _('no changes needed to %s\n')),
3405 'noop': (None, _('no changes needed to %s\n')),
3396 'unknown': (None, _('file not managed: %s\n')),
3406 'unknown': (None, _('file not managed: %s\n')),
3397 }
3407 }
3398
3408
3399 # "constant" that convey the backup strategy.
3409 # "constant" that convey the backup strategy.
3400 # All set to `discard` if `no-backup` is set do avoid checking
3410 # All set to `discard` if `no-backup` is set do avoid checking
3401 # no_backup lower in the code.
3411 # no_backup lower in the code.
3402 # These values are ordered for comparison purposes
3412 # These values are ordered for comparison purposes
3403 backupinteractive = 3 # do backup if interactively modified
3413 backupinteractive = 3 # do backup if interactively modified
3404 backup = 2 # unconditionally do backup
3414 backup = 2 # unconditionally do backup
3405 check = 1 # check if the existing file differs from target
3415 check = 1 # check if the existing file differs from target
3406 discard = 0 # never do backup
3416 discard = 0 # never do backup
3407 if opts.get('no_backup'):
3417 if opts.get('no_backup'):
3408 backupinteractive = backup = check = discard
3418 backupinteractive = backup = check = discard
3409 if interactive:
3419 if interactive:
3410 dsmodifiedbackup = backupinteractive
3420 dsmodifiedbackup = backupinteractive
3411 else:
3421 else:
3412 dsmodifiedbackup = backup
3422 dsmodifiedbackup = backup
3413 tobackup = set()
3423 tobackup = set()
3414
3424
3415 backupanddel = actions['remove']
3425 backupanddel = actions['remove']
3416 if not opts.get('no_backup'):
3426 if not opts.get('no_backup'):
3417 backupanddel = actions['drop']
3427 backupanddel = actions['drop']
3418
3428
3419 disptable = (
3429 disptable = (
3420 # dispatch table:
3430 # dispatch table:
3421 # file state
3431 # file state
3422 # action
3432 # action
3423 # make backup
3433 # make backup
3424
3434
3425 ## Sets that results that will change file on disk
3435 ## Sets that results that will change file on disk
3426 # Modified compared to target, no local change
3436 # Modified compared to target, no local change
3427 (modified, actions['revert'], discard),
3437 (modified, actions['revert'], discard),
3428 # Modified compared to target, but local file is deleted
3438 # Modified compared to target, but local file is deleted
3429 (deleted, actions['revert'], discard),
3439 (deleted, actions['revert'], discard),
3430 # Modified compared to target, local change
3440 # Modified compared to target, local change
3431 (dsmodified, actions['revert'], dsmodifiedbackup),
3441 (dsmodified, actions['revert'], dsmodifiedbackup),
3432 # Added since target
3442 # Added since target
3433 (added, actions['remove'], discard),
3443 (added, actions['remove'], discard),
3434 # Added in working directory
3444 # Added in working directory
3435 (dsadded, actions['forget'], discard),
3445 (dsadded, actions['forget'], discard),
3436 # Added since target, have local modification
3446 # Added since target, have local modification
3437 (modadded, backupanddel, backup),
3447 (modadded, backupanddel, backup),
3438 # Added since target but file is missing in working directory
3448 # Added since target but file is missing in working directory
3439 (deladded, actions['drop'], discard),
3449 (deladded, actions['drop'], discard),
3440 # Removed since target, before working copy parent
3450 # Removed since target, before working copy parent
3441 (removed, actions['add'], discard),
3451 (removed, actions['add'], discard),
3442 # Same as `removed` but an unknown file exists at the same path
3452 # Same as `removed` but an unknown file exists at the same path
3443 (removunk, actions['add'], check),
3453 (removunk, actions['add'], check),
3444 # Removed since targe, marked as such in working copy parent
3454 # Removed since targe, marked as such in working copy parent
3445 (dsremoved, actions['undelete'], discard),
3455 (dsremoved, actions['undelete'], discard),
3446 # Same as `dsremoved` but an unknown file exists at the same path
3456 # Same as `dsremoved` but an unknown file exists at the same path
3447 (dsremovunk, actions['undelete'], check),
3457 (dsremovunk, actions['undelete'], check),
3448 ## the following sets does not result in any file changes
3458 ## the following sets does not result in any file changes
3449 # File with no modification
3459 # File with no modification
3450 (clean, actions['noop'], discard),
3460 (clean, actions['noop'], discard),
3451 # Existing file, not tracked anywhere
3461 # Existing file, not tracked anywhere
3452 (unknown, actions['unknown'], discard),
3462 (unknown, actions['unknown'], discard),
3453 )
3463 )
3454
3464
3455 for abs, (rel, exact) in sorted(names.items()):
3465 for abs, (rel, exact) in sorted(names.items()):
3456 # target file to be touch on disk (relative to cwd)
3466 # target file to be touch on disk (relative to cwd)
3457 target = repo.wjoin(abs)
3467 target = repo.wjoin(abs)
3458 # search the entry in the dispatch table.
3468 # search the entry in the dispatch table.
3459 # if the file is in any of these sets, it was touched in the working
3469 # if the file is in any of these sets, it was touched in the working
3460 # directory parent and we are sure it needs to be reverted.
3470 # directory parent and we are sure it needs to be reverted.
3461 for table, (xlist, msg), dobackup in disptable:
3471 for table, (xlist, msg), dobackup in disptable:
3462 if abs not in table:
3472 if abs not in table:
3463 continue
3473 continue
3464 if xlist is not None:
3474 if xlist is not None:
3465 xlist.append(abs)
3475 xlist.append(abs)
3466 if dobackup:
3476 if dobackup:
3467 # If in interactive mode, don't automatically create
3477 # If in interactive mode, don't automatically create
3468 # .orig files (issue4793)
3478 # .orig files (issue4793)
3469 if dobackup == backupinteractive:
3479 if dobackup == backupinteractive:
3470 tobackup.add(abs)
3480 tobackup.add(abs)
3471 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3481 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3472 bakname = scmutil.origpath(ui, repo, rel)
3482 bakname = scmutil.origpath(ui, repo, rel)
3473 ui.note(_('saving current version of %s as %s\n') %
3483 ui.note(_('saving current version of %s as %s\n') %
3474 (rel, bakname))
3484 (rel, bakname))
3475 if not opts.get('dry_run'):
3485 if not opts.get('dry_run'):
3476 if interactive:
3486 if interactive:
3477 util.copyfile(target, bakname)
3487 util.copyfile(target, bakname)
3478 else:
3488 else:
3479 util.rename(target, bakname)
3489 util.rename(target, bakname)
3480 if ui.verbose or not exact:
3490 if ui.verbose or not exact:
3481 if not isinstance(msg, basestring):
3491 if not isinstance(msg, basestring):
3482 msg = msg(abs)
3492 msg = msg(abs)
3483 ui.status(msg % rel)
3493 ui.status(msg % rel)
3484 elif exact:
3494 elif exact:
3485 ui.warn(msg % rel)
3495 ui.warn(msg % rel)
3486 break
3496 break
3487
3497
3488 if not opts.get('dry_run'):
3498 if not opts.get('dry_run'):
3489 needdata = ('revert', 'add', 'undelete')
3499 needdata = ('revert', 'add', 'undelete')
3490 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3500 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3491 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3501 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3492
3502
3493 if targetsubs:
3503 if targetsubs:
3494 # Revert the subrepos on the revert list
3504 # Revert the subrepos on the revert list
3495 for sub in targetsubs:
3505 for sub in targetsubs:
3496 try:
3506 try:
3497 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3507 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3498 except KeyError:
3508 except KeyError:
3499 raise error.Abort("subrepository '%s' does not exist in %s!"
3509 raise error.Abort("subrepository '%s' does not exist in %s!"
3500 % (sub, short(ctx.node())))
3510 % (sub, short(ctx.node())))
3501
3511
3502 def _revertprefetch(repo, ctx, *files):
3512 def _revertprefetch(repo, ctx, *files):
3503 """Let extension changing the storage layer prefetch content"""
3513 """Let extension changing the storage layer prefetch content"""
3504 pass
3514 pass
3505
3515
3506 def _performrevert(repo, parents, ctx, actions, interactive=False,
3516 def _performrevert(repo, parents, ctx, actions, interactive=False,
3507 tobackup=None):
3517 tobackup=None):
3508 """function that actually perform all the actions computed for revert
3518 """function that actually perform all the actions computed for revert
3509
3519
3510 This is an independent function to let extension to plug in and react to
3520 This is an independent function to let extension to plug in and react to
3511 the imminent revert.
3521 the imminent revert.
3512
3522
3513 Make sure you have the working directory locked when calling this function.
3523 Make sure you have the working directory locked when calling this function.
3514 """
3524 """
3515 parent, p2 = parents
3525 parent, p2 = parents
3516 node = ctx.node()
3526 node = ctx.node()
3517 excluded_files = []
3527 excluded_files = []
3518 matcher_opts = {"exclude": excluded_files}
3528 matcher_opts = {"exclude": excluded_files}
3519
3529
3520 def checkout(f):
3530 def checkout(f):
3521 fc = ctx[f]
3531 fc = ctx[f]
3522 repo.wwrite(f, fc.data(), fc.flags())
3532 repo.wwrite(f, fc.data(), fc.flags())
3523
3533
3524 def doremove(f):
3534 def doremove(f):
3525 try:
3535 try:
3526 repo.wvfs.unlinkpath(f)
3536 repo.wvfs.unlinkpath(f)
3527 except OSError:
3537 except OSError:
3528 pass
3538 pass
3529 repo.dirstate.remove(f)
3539 repo.dirstate.remove(f)
3530
3540
3531 audit_path = pathutil.pathauditor(repo.root)
3541 audit_path = pathutil.pathauditor(repo.root)
3532 for f in actions['forget'][0]:
3542 for f in actions['forget'][0]:
3533 if interactive:
3543 if interactive:
3534 choice = repo.ui.promptchoice(
3544 choice = repo.ui.promptchoice(
3535 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3545 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3536 if choice == 0:
3546 if choice == 0:
3537 repo.dirstate.drop(f)
3547 repo.dirstate.drop(f)
3538 else:
3548 else:
3539 excluded_files.append(repo.wjoin(f))
3549 excluded_files.append(repo.wjoin(f))
3540 else:
3550 else:
3541 repo.dirstate.drop(f)
3551 repo.dirstate.drop(f)
3542 for f in actions['remove'][0]:
3552 for f in actions['remove'][0]:
3543 audit_path(f)
3553 audit_path(f)
3544 if interactive:
3554 if interactive:
3545 choice = repo.ui.promptchoice(
3555 choice = repo.ui.promptchoice(
3546 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3556 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3547 if choice == 0:
3557 if choice == 0:
3548 doremove(f)
3558 doremove(f)
3549 else:
3559 else:
3550 excluded_files.append(repo.wjoin(f))
3560 excluded_files.append(repo.wjoin(f))
3551 else:
3561 else:
3552 doremove(f)
3562 doremove(f)
3553 for f in actions['drop'][0]:
3563 for f in actions['drop'][0]:
3554 audit_path(f)
3564 audit_path(f)
3555 repo.dirstate.remove(f)
3565 repo.dirstate.remove(f)
3556
3566
3557 normal = None
3567 normal = None
3558 if node == parent:
3568 if node == parent:
3559 # We're reverting to our parent. If possible, we'd like status
3569 # We're reverting to our parent. If possible, we'd like status
3560 # to report the file as clean. We have to use normallookup for
3570 # to report the file as clean. We have to use normallookup for
3561 # merges to avoid losing information about merged/dirty files.
3571 # merges to avoid losing information about merged/dirty files.
3562 if p2 != nullid:
3572 if p2 != nullid:
3563 normal = repo.dirstate.normallookup
3573 normal = repo.dirstate.normallookup
3564 else:
3574 else:
3565 normal = repo.dirstate.normal
3575 normal = repo.dirstate.normal
3566
3576
3567 newlyaddedandmodifiedfiles = set()
3577 newlyaddedandmodifiedfiles = set()
3568 if interactive:
3578 if interactive:
3569 # Prompt the user for changes to revert
3579 # Prompt the user for changes to revert
3570 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3580 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3571 m = scmutil.match(ctx, torevert, matcher_opts)
3581 m = scmutil.match(ctx, torevert, matcher_opts)
3572 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3582 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3573 diffopts.nodates = True
3583 diffopts.nodates = True
3574 diffopts.git = True
3584 diffopts.git = True
3575 operation = 'discard'
3585 operation = 'discard'
3576 reversehunks = True
3586 reversehunks = True
3577 if node != parent:
3587 if node != parent:
3578 operation = 'revert'
3588 operation = 'revert'
3579 reversehunks = repo.ui.configbool('experimental',
3589 reversehunks = repo.ui.configbool('experimental',
3580 'revertalternateinteractivemode')
3590 'revertalternateinteractivemode')
3581 if reversehunks:
3591 if reversehunks:
3582 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3592 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3583 else:
3593 else:
3584 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3594 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3585 originalchunks = patch.parsepatch(diff)
3595 originalchunks = patch.parsepatch(diff)
3586
3596
3587 try:
3597 try:
3588
3598
3589 chunks, opts = recordfilter(repo.ui, originalchunks,
3599 chunks, opts = recordfilter(repo.ui, originalchunks,
3590 operation=operation)
3600 operation=operation)
3591 if reversehunks:
3601 if reversehunks:
3592 chunks = patch.reversehunks(chunks)
3602 chunks = patch.reversehunks(chunks)
3593
3603
3594 except patch.PatchError as err:
3604 except patch.PatchError as err:
3595 raise error.Abort(_('error parsing patch: %s') % err)
3605 raise error.Abort(_('error parsing patch: %s') % err)
3596
3606
3597 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3607 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3598 if tobackup is None:
3608 if tobackup is None:
3599 tobackup = set()
3609 tobackup = set()
3600 # Apply changes
3610 # Apply changes
3601 fp = stringio()
3611 fp = stringio()
3602 for c in chunks:
3612 for c in chunks:
3603 # Create a backup file only if this hunk should be backed up
3613 # Create a backup file only if this hunk should be backed up
3604 if ishunk(c) and c.header.filename() in tobackup:
3614 if ishunk(c) and c.header.filename() in tobackup:
3605 abs = c.header.filename()
3615 abs = c.header.filename()
3606 target = repo.wjoin(abs)
3616 target = repo.wjoin(abs)
3607 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3617 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3608 util.copyfile(target, bakname)
3618 util.copyfile(target, bakname)
3609 tobackup.remove(abs)
3619 tobackup.remove(abs)
3610 c.write(fp)
3620 c.write(fp)
3611 dopatch = fp.tell()
3621 dopatch = fp.tell()
3612 fp.seek(0)
3622 fp.seek(0)
3613 if dopatch:
3623 if dopatch:
3614 try:
3624 try:
3615 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3625 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3616 except patch.PatchError as err:
3626 except patch.PatchError as err:
3617 raise error.Abort(str(err))
3627 raise error.Abort(str(err))
3618 del fp
3628 del fp
3619 else:
3629 else:
3620 for f in actions['revert'][0]:
3630 for f in actions['revert'][0]:
3621 checkout(f)
3631 checkout(f)
3622 if normal:
3632 if normal:
3623 normal(f)
3633 normal(f)
3624
3634
3625 for f in actions['add'][0]:
3635 for f in actions['add'][0]:
3626 # Don't checkout modified files, they are already created by the diff
3636 # Don't checkout modified files, they are already created by the diff
3627 if f not in newlyaddedandmodifiedfiles:
3637 if f not in newlyaddedandmodifiedfiles:
3628 checkout(f)
3638 checkout(f)
3629 repo.dirstate.add(f)
3639 repo.dirstate.add(f)
3630
3640
3631 normal = repo.dirstate.normallookup
3641 normal = repo.dirstate.normallookup
3632 if node == parent and p2 == nullid:
3642 if node == parent and p2 == nullid:
3633 normal = repo.dirstate.normal
3643 normal = repo.dirstate.normal
3634 for f in actions['undelete'][0]:
3644 for f in actions['undelete'][0]:
3635 checkout(f)
3645 checkout(f)
3636 normal(f)
3646 normal(f)
3637
3647
3638 copied = copies.pathcopies(repo[parent], ctx)
3648 copied = copies.pathcopies(repo[parent], ctx)
3639
3649
3640 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3650 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3641 if f in copied:
3651 if f in copied:
3642 repo.dirstate.copy(copied[f], f)
3652 repo.dirstate.copy(copied[f], f)
3643
3653
3644 class command(registrar.command):
3654 class command(registrar.command):
3645 def _doregister(self, func, name, *args, **kwargs):
3655 def _doregister(self, func, name, *args, **kwargs):
3646 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3656 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3647 return super(command, self)._doregister(func, name, *args, **kwargs)
3657 return super(command, self)._doregister(func, name, *args, **kwargs)
3648
3658
3649 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3659 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3650 # commands.outgoing. "missing" is "missing" of the result of
3660 # commands.outgoing. "missing" is "missing" of the result of
3651 # "findcommonoutgoing()"
3661 # "findcommonoutgoing()"
3652 outgoinghooks = util.hooks()
3662 outgoinghooks = util.hooks()
3653
3663
3654 # a list of (ui, repo) functions called by commands.summary
3664 # a list of (ui, repo) functions called by commands.summary
3655 summaryhooks = util.hooks()
3665 summaryhooks = util.hooks()
3656
3666
3657 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3667 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3658 #
3668 #
3659 # functions should return tuple of booleans below, if 'changes' is None:
3669 # functions should return tuple of booleans below, if 'changes' is None:
3660 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3670 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3661 #
3671 #
3662 # otherwise, 'changes' is a tuple of tuples below:
3672 # otherwise, 'changes' is a tuple of tuples below:
3663 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3673 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3664 # - (desturl, destbranch, destpeer, outgoing)
3674 # - (desturl, destbranch, destpeer, outgoing)
3665 summaryremotehooks = util.hooks()
3675 summaryremotehooks = util.hooks()
3666
3676
3667 # A list of state files kept by multistep operations like graft.
3677 # A list of state files kept by multistep operations like graft.
3668 # Since graft cannot be aborted, it is considered 'clearable' by update.
3678 # Since graft cannot be aborted, it is considered 'clearable' by update.
3669 # note: bisect is intentionally excluded
3679 # note: bisect is intentionally excluded
3670 # (state file, clearable, allowcommit, error, hint)
3680 # (state file, clearable, allowcommit, error, hint)
3671 unfinishedstates = [
3681 unfinishedstates = [
3672 ('graftstate', True, False, _('graft in progress'),
3682 ('graftstate', True, False, _('graft in progress'),
3673 _("use 'hg graft --continue' or 'hg update' to abort")),
3683 _("use 'hg graft --continue' or 'hg update' to abort")),
3674 ('updatestate', True, False, _('last update was interrupted'),
3684 ('updatestate', True, False, _('last update was interrupted'),
3675 _("use 'hg update' to get a consistent checkout"))
3685 _("use 'hg update' to get a consistent checkout"))
3676 ]
3686 ]
3677
3687
3678 def checkunfinished(repo, commit=False):
3688 def checkunfinished(repo, commit=False):
3679 '''Look for an unfinished multistep operation, like graft, and abort
3689 '''Look for an unfinished multistep operation, like graft, and abort
3680 if found. It's probably good to check this right before
3690 if found. It's probably good to check this right before
3681 bailifchanged().
3691 bailifchanged().
3682 '''
3692 '''
3683 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3693 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3684 if commit and allowcommit:
3694 if commit and allowcommit:
3685 continue
3695 continue
3686 if repo.vfs.exists(f):
3696 if repo.vfs.exists(f):
3687 raise error.Abort(msg, hint=hint)
3697 raise error.Abort(msg, hint=hint)
3688
3698
3689 def clearunfinished(repo):
3699 def clearunfinished(repo):
3690 '''Check for unfinished operations (as above), and clear the ones
3700 '''Check for unfinished operations (as above), and clear the ones
3691 that are clearable.
3701 that are clearable.
3692 '''
3702 '''
3693 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3703 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3694 if not clearable and repo.vfs.exists(f):
3704 if not clearable and repo.vfs.exists(f):
3695 raise error.Abort(msg, hint=hint)
3705 raise error.Abort(msg, hint=hint)
3696 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3706 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3697 if clearable and repo.vfs.exists(f):
3707 if clearable and repo.vfs.exists(f):
3698 util.unlink(repo.vfs.join(f))
3708 util.unlink(repo.vfs.join(f))
3699
3709
3700 afterresolvedstates = [
3710 afterresolvedstates = [
3701 ('graftstate',
3711 ('graftstate',
3702 _('hg graft --continue')),
3712 _('hg graft --continue')),
3703 ]
3713 ]
3704
3714
3705 def howtocontinue(repo):
3715 def howtocontinue(repo):
3706 '''Check for an unfinished operation and return the command to finish
3716 '''Check for an unfinished operation and return the command to finish
3707 it.
3717 it.
3708
3718
3709 afterresolvedstates tuples define a .hg/{file} and the corresponding
3719 afterresolvedstates tuples define a .hg/{file} and the corresponding
3710 command needed to finish it.
3720 command needed to finish it.
3711
3721
3712 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3722 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3713 a boolean.
3723 a boolean.
3714 '''
3724 '''
3715 contmsg = _("continue: %s")
3725 contmsg = _("continue: %s")
3716 for f, msg in afterresolvedstates:
3726 for f, msg in afterresolvedstates:
3717 if repo.vfs.exists(f):
3727 if repo.vfs.exists(f):
3718 return contmsg % msg, True
3728 return contmsg % msg, True
3719 if repo[None].dirty(missing=True, merge=False, branch=False):
3729 if repo[None].dirty(missing=True, merge=False, branch=False):
3720 return contmsg % _("hg commit"), False
3730 return contmsg % _("hg commit"), False
3721 return None, None
3731 return None, None
3722
3732
3723 def checkafterresolved(repo):
3733 def checkafterresolved(repo):
3724 '''Inform the user about the next action after completing hg resolve
3734 '''Inform the user about the next action after completing hg resolve
3725
3735
3726 If there's a matching afterresolvedstates, howtocontinue will yield
3736 If there's a matching afterresolvedstates, howtocontinue will yield
3727 repo.ui.warn as the reporter.
3737 repo.ui.warn as the reporter.
3728
3738
3729 Otherwise, it will yield repo.ui.note.
3739 Otherwise, it will yield repo.ui.note.
3730 '''
3740 '''
3731 msg, warning = howtocontinue(repo)
3741 msg, warning = howtocontinue(repo)
3732 if msg is not None:
3742 if msg is not None:
3733 if warning:
3743 if warning:
3734 repo.ui.warn("%s\n" % msg)
3744 repo.ui.warn("%s\n" % msg)
3735 else:
3745 else:
3736 repo.ui.note("%s\n" % msg)
3746 repo.ui.note("%s\n" % msg)
3737
3747
3738 def wrongtooltocontinue(repo, task):
3748 def wrongtooltocontinue(repo, task):
3739 '''Raise an abort suggesting how to properly continue if there is an
3749 '''Raise an abort suggesting how to properly continue if there is an
3740 active task.
3750 active task.
3741
3751
3742 Uses howtocontinue() to find the active task.
3752 Uses howtocontinue() to find the active task.
3743
3753
3744 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3754 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3745 a hint.
3755 a hint.
3746 '''
3756 '''
3747 after = howtocontinue(repo)
3757 after = howtocontinue(repo)
3748 hint = None
3758 hint = None
3749 if after[1]:
3759 if after[1]:
3750 hint = after[0]
3760 hint = after[0]
3751 raise error.Abort(_('no %s in progress') % task, hint=hint)
3761 raise error.Abort(_('no %s in progress') % task, hint=hint)
@@ -1,828 +1,828 b''
1 commit date test
1 commit date test
2
2
3 $ hg init test
3 $ hg init test
4 $ cd test
4 $ cd test
5 $ echo foo > foo
5 $ echo foo > foo
6 $ hg add foo
6 $ hg add foo
7 $ cat > $TESTTMP/checkeditform.sh <<EOF
7 $ cat > $TESTTMP/checkeditform.sh <<EOF
8 > env | grep HGEDITFORM
8 > env | grep HGEDITFORM
9 > true
9 > true
10 > EOF
10 > EOF
11 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg commit -m ""
11 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg commit -m ""
12 HGEDITFORM=commit.normal.normal
12 HGEDITFORM=commit.normal.normal
13 abort: empty commit message
13 abort: empty commit message
14 [255]
14 [255]
15 $ hg commit -d '0 0' -m commit-1
15 $ hg commit -d '0 0' -m commit-1
16 $ echo foo >> foo
16 $ echo foo >> foo
17 $ hg commit -d '1 4444444' -m commit-3
17 $ hg commit -d '1 4444444' -m commit-3
18 hg: parse error: impossible time zone offset: 4444444
18 hg: parse error: impossible time zone offset: 4444444
19 [255]
19 [255]
20 $ hg commit -d '1 15.1' -m commit-4
20 $ hg commit -d '1 15.1' -m commit-4
21 hg: parse error: invalid date: '1\t15.1'
21 hg: parse error: invalid date: '1\t15.1'
22 [255]
22 [255]
23 $ hg commit -d 'foo bar' -m commit-5
23 $ hg commit -d 'foo bar' -m commit-5
24 hg: parse error: invalid date: 'foo bar'
24 hg: parse error: invalid date: 'foo bar'
25 [255]
25 [255]
26 $ hg commit -d ' 1 4444' -m commit-6
26 $ hg commit -d ' 1 4444' -m commit-6
27 $ hg commit -d '111111111111 0' -m commit-7
27 $ hg commit -d '111111111111 0' -m commit-7
28 hg: parse error: date exceeds 32 bits: 111111111111
28 hg: parse error: date exceeds 32 bits: 111111111111
29 [255]
29 [255]
30 $ hg commit -d '-111111111111 0' -m commit-7
30 $ hg commit -d '-111111111111 0' -m commit-7
31 hg: parse error: date exceeds 32 bits: -111111111111
31 hg: parse error: date exceeds 32 bits: -111111111111
32 [255]
32 [255]
33 $ echo foo >> foo
33 $ echo foo >> foo
34 $ hg commit -d '1901-12-13 20:45:52 +0000' -m commit-7-2
34 $ hg commit -d '1901-12-13 20:45:52 +0000' -m commit-7-2
35 $ echo foo >> foo
35 $ echo foo >> foo
36 $ hg commit -d '-2147483648 0' -m commit-7-3
36 $ hg commit -d '-2147483648 0' -m commit-7-3
37 $ hg log -T '{rev} {date|isodatesec}\n' -l2
37 $ hg log -T '{rev} {date|isodatesec}\n' -l2
38 3 1901-12-13 20:45:52 +0000
38 3 1901-12-13 20:45:52 +0000
39 2 1901-12-13 20:45:52 +0000
39 2 1901-12-13 20:45:52 +0000
40 $ hg commit -d '1901-12-13 20:45:51 +0000' -m commit-7
40 $ hg commit -d '1901-12-13 20:45:51 +0000' -m commit-7
41 hg: parse error: date exceeds 32 bits: -2147483649
41 hg: parse error: date exceeds 32 bits: -2147483649
42 [255]
42 [255]
43 $ hg commit -d '-2147483649 0' -m commit-7
43 $ hg commit -d '-2147483649 0' -m commit-7
44 hg: parse error: date exceeds 32 bits: -2147483649
44 hg: parse error: date exceeds 32 bits: -2147483649
45 [255]
45 [255]
46
46
47 commit added file that has been deleted
47 commit added file that has been deleted
48
48
49 $ echo bar > bar
49 $ echo bar > bar
50 $ hg add bar
50 $ hg add bar
51 $ rm bar
51 $ rm bar
52 $ hg commit -m commit-8
52 $ hg commit -m commit-8
53 nothing changed (1 missing files, see 'hg status')
53 nothing changed (1 missing files, see 'hg status')
54 [1]
54 [1]
55 $ hg commit -m commit-8-2 bar
55 $ hg commit -m commit-8-2 bar
56 abort: bar: file not found!
56 abort: bar: file not found!
57 [255]
57 [255]
58
58
59 $ hg -q revert -a --no-backup
59 $ hg -q revert -a --no-backup
60
60
61 $ mkdir dir
61 $ mkdir dir
62 $ echo boo > dir/file
62 $ echo boo > dir/file
63 $ hg add
63 $ hg add
64 adding dir/file (glob)
64 adding dir/file (glob)
65 $ hg -v commit -m commit-9 dir
65 $ hg -v commit -m commit-9 dir
66 committing files:
66 committing files:
67 dir/file
67 dir/file
68 committing manifest
68 committing manifest
69 committing changelog
69 committing changelog
70 committed changeset 4:1957363f1ced
70 committed changeset 4:1957363f1ced
71
71
72 $ echo > dir.file
72 $ echo > dir.file
73 $ hg add
73 $ hg add
74 adding dir.file
74 adding dir.file
75 $ hg commit -m commit-10 dir dir.file
75 $ hg commit -m commit-10 dir dir.file
76 abort: dir: no match under directory!
76 abort: dir: no match under directory!
77 [255]
77 [255]
78
78
79 $ echo >> dir/file
79 $ echo >> dir/file
80 $ mkdir bleh
80 $ mkdir bleh
81 $ mkdir dir2
81 $ mkdir dir2
82 $ cd bleh
82 $ cd bleh
83 $ hg commit -m commit-11 .
83 $ hg commit -m commit-11 .
84 abort: bleh: no match under directory!
84 abort: bleh: no match under directory!
85 [255]
85 [255]
86 $ hg commit -m commit-12 ../dir ../dir2
86 $ hg commit -m commit-12 ../dir ../dir2
87 abort: dir2: no match under directory!
87 abort: dir2: no match under directory!
88 [255]
88 [255]
89 $ hg -v commit -m commit-13 ../dir
89 $ hg -v commit -m commit-13 ../dir
90 committing files:
90 committing files:
91 dir/file
91 dir/file
92 committing manifest
92 committing manifest
93 committing changelog
93 committing changelog
94 committed changeset 5:a31d8f87544a
94 committed changeset 5:a31d8f87544a
95 $ cd ..
95 $ cd ..
96
96
97 $ hg commit -m commit-14 does-not-exist
97 $ hg commit -m commit-14 does-not-exist
98 abort: does-not-exist: * (glob)
98 abort: does-not-exist: * (glob)
99 [255]
99 [255]
100
100
101 #if symlink
101 #if symlink
102 $ ln -s foo baz
102 $ ln -s foo baz
103 $ hg commit -m commit-15 baz
103 $ hg commit -m commit-15 baz
104 abort: baz: file not tracked!
104 abort: baz: file not tracked!
105 [255]
105 [255]
106 $ rm baz
106 $ rm baz
107 #endif
107 #endif
108
108
109 $ touch quux
109 $ touch quux
110 $ hg commit -m commit-16 quux
110 $ hg commit -m commit-16 quux
111 abort: quux: file not tracked!
111 abort: quux: file not tracked!
112 [255]
112 [255]
113 $ echo >> dir/file
113 $ echo >> dir/file
114 $ hg -v commit -m commit-17 dir/file
114 $ hg -v commit -m commit-17 dir/file
115 committing files:
115 committing files:
116 dir/file
116 dir/file
117 committing manifest
117 committing manifest
118 committing changelog
118 committing changelog
119 committed changeset 6:32d054c9d085
119 committed changeset 6:32d054c9d085
120
120
121 An empty date was interpreted as epoch origin
121 An empty date was interpreted as epoch origin
122
122
123 $ echo foo >> foo
123 $ echo foo >> foo
124 $ hg commit -d '' -m commit-no-date --config devel.default-date=
124 $ hg commit -d '' -m commit-no-date --config devel.default-date=
125 $ hg tip --template '{date|isodate}\n' | grep '1970'
125 $ hg tip --template '{date|isodate}\n' | grep '1970'
126 [1]
126 [1]
127
127
128 Using the advanced --extra flag
128 Using the advanced --extra flag
129
129
130 $ echo "[extensions]" >> $HGRCPATH
130 $ echo "[extensions]" >> $HGRCPATH
131 $ echo "commitextras=" >> $HGRCPATH
131 $ echo "commitextras=" >> $HGRCPATH
132 $ hg status
132 $ hg status
133 ? quux
133 ? quux
134 $ hg add quux
134 $ hg add quux
135 $ hg commit -m "adding internal used extras" --extra amend_source=hash
135 $ hg commit -m "adding internal used extras" --extra amend_source=hash
136 abort: key 'amend_source' is used internally, can't be set manually
136 abort: key 'amend_source' is used internally, can't be set manually
137 [255]
137 [255]
138 $ hg commit -m "special chars in extra" --extra id@phab=214
138 $ hg commit -m "special chars in extra" --extra id@phab=214
139 abort: keys can only contain ascii letters, digits, '_' and '-'
139 abort: keys can only contain ascii letters, digits, '_' and '-'
140 [255]
140 [255]
141 $ hg commit -m "empty key" --extra =value
141 $ hg commit -m "empty key" --extra =value
142 abort: unable to parse '=value', keys can't be empty
142 abort: unable to parse '=value', keys can't be empty
143 [255]
143 [255]
144 $ hg commit -m "adding extras" --extra sourcehash=foo --extra oldhash=bar
144 $ hg commit -m "adding extras" --extra sourcehash=foo --extra oldhash=bar
145 $ hg log -r . -T '{extras % "{extra}\n"}'
145 $ hg log -r . -T '{extras % "{extra}\n"}'
146 branch=default
146 branch=default
147 oldhash=bar
147 oldhash=bar
148 sourcehash=foo
148 sourcehash=foo
149
149
150 Failed commit with --addremove should not update dirstate
150 Failed commit with --addremove should not update dirstate
151
151
152 $ echo foo > newfile
152 $ echo foo > newfile
153 $ hg status
153 $ hg status
154 ? newfile
154 ? newfile
155 $ HGEDITOR=false hg ci --addremove
155 $ HGEDITOR=false hg ci --addremove
156 adding newfile
156 adding newfile
157 abort: edit failed: false exited with status 1
157 abort: edit failed: false exited with status 1
158 [255]
158 [255]
159 $ hg status
159 $ hg status
160 A newfile
160 ? newfile
161
161
162 Make sure we do not obscure unknown requires file entries (issue2649)
162 Make sure we do not obscure unknown requires file entries (issue2649)
163
163
164 $ echo foo >> foo
164 $ echo foo >> foo
165 $ echo fake >> .hg/requires
165 $ echo fake >> .hg/requires
166 $ hg commit -m bla
166 $ hg commit -m bla
167 abort: repository requires features unknown to this Mercurial: fake!
167 abort: repository requires features unknown to this Mercurial: fake!
168 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
168 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
169 [255]
169 [255]
170
170
171 $ cd ..
171 $ cd ..
172
172
173
173
174 partial subdir commit test
174 partial subdir commit test
175
175
176 $ hg init test2
176 $ hg init test2
177 $ cd test2
177 $ cd test2
178 $ mkdir foo
178 $ mkdir foo
179 $ echo foo > foo/foo
179 $ echo foo > foo/foo
180 $ mkdir bar
180 $ mkdir bar
181 $ echo bar > bar/bar
181 $ echo bar > bar/bar
182 $ hg add
182 $ hg add
183 adding bar/bar (glob)
183 adding bar/bar (glob)
184 adding foo/foo (glob)
184 adding foo/foo (glob)
185 $ HGEDITOR=cat hg ci -e -m commit-subdir-1 foo
185 $ HGEDITOR=cat hg ci -e -m commit-subdir-1 foo
186 commit-subdir-1
186 commit-subdir-1
187
187
188
188
189 HG: Enter commit message. Lines beginning with 'HG:' are removed.
189 HG: Enter commit message. Lines beginning with 'HG:' are removed.
190 HG: Leave message empty to abort commit.
190 HG: Leave message empty to abort commit.
191 HG: --
191 HG: --
192 HG: user: test
192 HG: user: test
193 HG: branch 'default'
193 HG: branch 'default'
194 HG: added foo/foo
194 HG: added foo/foo
195
195
196
196
197 $ hg ci -m commit-subdir-2 bar
197 $ hg ci -m commit-subdir-2 bar
198
198
199 subdir log 1
199 subdir log 1
200
200
201 $ hg log -v foo
201 $ hg log -v foo
202 changeset: 0:f97e73a25882
202 changeset: 0:f97e73a25882
203 user: test
203 user: test
204 date: Thu Jan 01 00:00:00 1970 +0000
204 date: Thu Jan 01 00:00:00 1970 +0000
205 files: foo/foo
205 files: foo/foo
206 description:
206 description:
207 commit-subdir-1
207 commit-subdir-1
208
208
209
209
210
210
211 subdir log 2
211 subdir log 2
212
212
213 $ hg log -v bar
213 $ hg log -v bar
214 changeset: 1:aa809156d50d
214 changeset: 1:aa809156d50d
215 tag: tip
215 tag: tip
216 user: test
216 user: test
217 date: Thu Jan 01 00:00:00 1970 +0000
217 date: Thu Jan 01 00:00:00 1970 +0000
218 files: bar/bar
218 files: bar/bar
219 description:
219 description:
220 commit-subdir-2
220 commit-subdir-2
221
221
222
222
223
223
224 full log
224 full log
225
225
226 $ hg log -v
226 $ hg log -v
227 changeset: 1:aa809156d50d
227 changeset: 1:aa809156d50d
228 tag: tip
228 tag: tip
229 user: test
229 user: test
230 date: Thu Jan 01 00:00:00 1970 +0000
230 date: Thu Jan 01 00:00:00 1970 +0000
231 files: bar/bar
231 files: bar/bar
232 description:
232 description:
233 commit-subdir-2
233 commit-subdir-2
234
234
235
235
236 changeset: 0:f97e73a25882
236 changeset: 0:f97e73a25882
237 user: test
237 user: test
238 date: Thu Jan 01 00:00:00 1970 +0000
238 date: Thu Jan 01 00:00:00 1970 +0000
239 files: foo/foo
239 files: foo/foo
240 description:
240 description:
241 commit-subdir-1
241 commit-subdir-1
242
242
243
243
244 $ cd ..
244 $ cd ..
245
245
246
246
247 dot and subdir commit test
247 dot and subdir commit test
248
248
249 $ hg init test3
249 $ hg init test3
250 $ echo commit-foo-subdir > commit-log-test
250 $ echo commit-foo-subdir > commit-log-test
251 $ cd test3
251 $ cd test3
252 $ mkdir foo
252 $ mkdir foo
253 $ echo foo content > foo/plain-file
253 $ echo foo content > foo/plain-file
254 $ hg add foo/plain-file
254 $ hg add foo/plain-file
255 $ HGEDITOR=cat hg ci --edit -l ../commit-log-test foo
255 $ HGEDITOR=cat hg ci --edit -l ../commit-log-test foo
256 commit-foo-subdir
256 commit-foo-subdir
257
257
258
258
259 HG: Enter commit message. Lines beginning with 'HG:' are removed.
259 HG: Enter commit message. Lines beginning with 'HG:' are removed.
260 HG: Leave message empty to abort commit.
260 HG: Leave message empty to abort commit.
261 HG: --
261 HG: --
262 HG: user: test
262 HG: user: test
263 HG: branch 'default'
263 HG: branch 'default'
264 HG: added foo/plain-file
264 HG: added foo/plain-file
265
265
266
266
267 $ echo modified foo content > foo/plain-file
267 $ echo modified foo content > foo/plain-file
268 $ hg ci -m commit-foo-dot .
268 $ hg ci -m commit-foo-dot .
269
269
270 full log
270 full log
271
271
272 $ hg log -v
272 $ hg log -v
273 changeset: 1:95b38e3a5b2e
273 changeset: 1:95b38e3a5b2e
274 tag: tip
274 tag: tip
275 user: test
275 user: test
276 date: Thu Jan 01 00:00:00 1970 +0000
276 date: Thu Jan 01 00:00:00 1970 +0000
277 files: foo/plain-file
277 files: foo/plain-file
278 description:
278 description:
279 commit-foo-dot
279 commit-foo-dot
280
280
281
281
282 changeset: 0:65d4e9386227
282 changeset: 0:65d4e9386227
283 user: test
283 user: test
284 date: Thu Jan 01 00:00:00 1970 +0000
284 date: Thu Jan 01 00:00:00 1970 +0000
285 files: foo/plain-file
285 files: foo/plain-file
286 description:
286 description:
287 commit-foo-subdir
287 commit-foo-subdir
288
288
289
289
290
290
291 subdir log
291 subdir log
292
292
293 $ cd foo
293 $ cd foo
294 $ hg log .
294 $ hg log .
295 changeset: 1:95b38e3a5b2e
295 changeset: 1:95b38e3a5b2e
296 tag: tip
296 tag: tip
297 user: test
297 user: test
298 date: Thu Jan 01 00:00:00 1970 +0000
298 date: Thu Jan 01 00:00:00 1970 +0000
299 summary: commit-foo-dot
299 summary: commit-foo-dot
300
300
301 changeset: 0:65d4e9386227
301 changeset: 0:65d4e9386227
302 user: test
302 user: test
303 date: Thu Jan 01 00:00:00 1970 +0000
303 date: Thu Jan 01 00:00:00 1970 +0000
304 summary: commit-foo-subdir
304 summary: commit-foo-subdir
305
305
306 $ cd ..
306 $ cd ..
307 $ cd ..
307 $ cd ..
308
308
309 Issue1049: Hg permits partial commit of merge without warning
309 Issue1049: Hg permits partial commit of merge without warning
310
310
311 $ hg init issue1049
311 $ hg init issue1049
312 $ cd issue1049
312 $ cd issue1049
313 $ echo a > a
313 $ echo a > a
314 $ hg ci -Ama
314 $ hg ci -Ama
315 adding a
315 adding a
316 $ echo a >> a
316 $ echo a >> a
317 $ hg ci -mb
317 $ hg ci -mb
318 $ hg up 0
318 $ hg up 0
319 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
319 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
320 $ echo b >> a
320 $ echo b >> a
321 $ hg ci -mc
321 $ hg ci -mc
322 created new head
322 created new head
323 $ HGMERGE=true hg merge
323 $ HGMERGE=true hg merge
324 merging a
324 merging a
325 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
325 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
326 (branch merge, don't forget to commit)
326 (branch merge, don't forget to commit)
327
327
328 should fail because we are specifying a file name
328 should fail because we are specifying a file name
329
329
330 $ hg ci -mmerge a
330 $ hg ci -mmerge a
331 abort: cannot partially commit a merge (do not specify files or patterns)
331 abort: cannot partially commit a merge (do not specify files or patterns)
332 [255]
332 [255]
333
333
334 should fail because we are specifying a pattern
334 should fail because we are specifying a pattern
335
335
336 $ hg ci -mmerge -I a
336 $ hg ci -mmerge -I a
337 abort: cannot partially commit a merge (do not specify files or patterns)
337 abort: cannot partially commit a merge (do not specify files or patterns)
338 [255]
338 [255]
339
339
340 should succeed
340 should succeed
341
341
342 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg ci -mmerge --edit
342 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg ci -mmerge --edit
343 HGEDITFORM=commit.normal.merge
343 HGEDITFORM=commit.normal.merge
344 $ cd ..
344 $ cd ..
345
345
346
346
347 test commit message content
347 test commit message content
348
348
349 $ hg init commitmsg
349 $ hg init commitmsg
350 $ cd commitmsg
350 $ cd commitmsg
351 $ echo changed > changed
351 $ echo changed > changed
352 $ echo removed > removed
352 $ echo removed > removed
353 $ hg book activebookmark
353 $ hg book activebookmark
354 $ hg ci -qAm init
354 $ hg ci -qAm init
355
355
356 $ hg rm removed
356 $ hg rm removed
357 $ echo changed >> changed
357 $ echo changed >> changed
358 $ echo added > added
358 $ echo added > added
359 $ hg add added
359 $ hg add added
360 $ HGEDITOR=cat hg ci -A
360 $ HGEDITOR=cat hg ci -A
361
361
362
362
363 HG: Enter commit message. Lines beginning with 'HG:' are removed.
363 HG: Enter commit message. Lines beginning with 'HG:' are removed.
364 HG: Leave message empty to abort commit.
364 HG: Leave message empty to abort commit.
365 HG: --
365 HG: --
366 HG: user: test
366 HG: user: test
367 HG: branch 'default'
367 HG: branch 'default'
368 HG: bookmark 'activebookmark'
368 HG: bookmark 'activebookmark'
369 HG: added added
369 HG: added added
370 HG: changed changed
370 HG: changed changed
371 HG: removed removed
371 HG: removed removed
372 abort: empty commit message
372 abort: empty commit message
373 [255]
373 [255]
374
374
375 test saving last-message.txt
375 test saving last-message.txt
376
376
377 $ hg init sub
377 $ hg init sub
378 $ echo a > sub/a
378 $ echo a > sub/a
379 $ hg -R sub add sub/a
379 $ hg -R sub add sub/a
380 $ cat > sub/.hg/hgrc <<EOF
380 $ cat > sub/.hg/hgrc <<EOF
381 > [hooks]
381 > [hooks]
382 > precommit.test-saving-last-message = false
382 > precommit.test-saving-last-message = false
383 > EOF
383 > EOF
384
384
385 $ echo 'sub = sub' > .hgsub
385 $ echo 'sub = sub' > .hgsub
386 $ hg add .hgsub
386 $ hg add .hgsub
387
387
388 $ cat > $TESTTMP/editor.sh <<EOF
388 $ cat > $TESTTMP/editor.sh <<EOF
389 > echo "==== before editing:"
389 > echo "==== before editing:"
390 > cat \$1
390 > cat \$1
391 > echo "===="
391 > echo "===="
392 > echo "test saving last-message.txt" >> \$1
392 > echo "test saving last-message.txt" >> \$1
393 > EOF
393 > EOF
394
394
395 $ rm -f .hg/last-message.txt
395 $ rm -f .hg/last-message.txt
396 $ HGEDITOR="sh $TESTTMP/editor.sh" hg commit -S -q
396 $ HGEDITOR="sh $TESTTMP/editor.sh" hg commit -S -q
397 ==== before editing:
397 ==== before editing:
398
398
399
399
400 HG: Enter commit message. Lines beginning with 'HG:' are removed.
400 HG: Enter commit message. Lines beginning with 'HG:' are removed.
401 HG: Leave message empty to abort commit.
401 HG: Leave message empty to abort commit.
402 HG: --
402 HG: --
403 HG: user: test
403 HG: user: test
404 HG: branch 'default'
404 HG: branch 'default'
405 HG: bookmark 'activebookmark'
405 HG: bookmark 'activebookmark'
406 HG: subrepo sub
406 HG: subrepo sub
407 HG: added .hgsub
407 HG: added .hgsub
408 HG: added added
408 HG: added added
409 HG: changed .hgsubstate
409 HG: changed .hgsubstate
410 HG: changed changed
410 HG: changed changed
411 HG: removed removed
411 HG: removed removed
412 ====
412 ====
413 abort: precommit.test-saving-last-message hook exited with status 1 (in subrepository "sub")
413 abort: precommit.test-saving-last-message hook exited with status 1 (in subrepository "sub")
414 [255]
414 [255]
415 $ cat .hg/last-message.txt
415 $ cat .hg/last-message.txt
416
416
417
417
418 test saving last-message.txt
418 test saving last-message.txt
419
419
420 test that '[committemplate] changeset' definition and commit log
420 test that '[committemplate] changeset' definition and commit log
421 specific template keywords work well
421 specific template keywords work well
422
422
423 $ cat >> .hg/hgrc <<EOF
423 $ cat >> .hg/hgrc <<EOF
424 > [committemplate]
424 > [committemplate]
425 > changeset.commit.normal = 'HG: this is "commit.normal" template
425 > changeset.commit.normal = 'HG: this is "commit.normal" template
426 > HG: {extramsg}
426 > HG: {extramsg}
427 > {if(activebookmark,
427 > {if(activebookmark,
428 > "HG: bookmark '{activebookmark}' is activated\n",
428 > "HG: bookmark '{activebookmark}' is activated\n",
429 > "HG: no bookmark is activated\n")}{subrepos %
429 > "HG: no bookmark is activated\n")}{subrepos %
430 > "HG: subrepo '{subrepo}' is changed\n"}'
430 > "HG: subrepo '{subrepo}' is changed\n"}'
431 >
431 >
432 > changeset.commit = HG: this is "commit" template
432 > changeset.commit = HG: this is "commit" template
433 > HG: {extramsg}
433 > HG: {extramsg}
434 > {if(activebookmark,
434 > {if(activebookmark,
435 > "HG: bookmark '{activebookmark}' is activated\n",
435 > "HG: bookmark '{activebookmark}' is activated\n",
436 > "HG: no bookmark is activated\n")}{subrepos %
436 > "HG: no bookmark is activated\n")}{subrepos %
437 > "HG: subrepo '{subrepo}' is changed\n"}
437 > "HG: subrepo '{subrepo}' is changed\n"}
438 >
438 >
439 > changeset = HG: this is customized commit template
439 > changeset = HG: this is customized commit template
440 > HG: {extramsg}
440 > HG: {extramsg}
441 > {if(activebookmark,
441 > {if(activebookmark,
442 > "HG: bookmark '{activebookmark}' is activated\n",
442 > "HG: bookmark '{activebookmark}' is activated\n",
443 > "HG: no bookmark is activated\n")}{subrepos %
443 > "HG: no bookmark is activated\n")}{subrepos %
444 > "HG: subrepo '{subrepo}' is changed\n"}
444 > "HG: subrepo '{subrepo}' is changed\n"}
445 > EOF
445 > EOF
446
446
447 $ hg init sub2
447 $ hg init sub2
448 $ echo a > sub2/a
448 $ echo a > sub2/a
449 $ hg -R sub2 add sub2/a
449 $ hg -R sub2 add sub2/a
450 $ echo 'sub2 = sub2' >> .hgsub
450 $ echo 'sub2 = sub2' >> .hgsub
451
451
452 $ HGEDITOR=cat hg commit -S -q
452 $ HGEDITOR=cat hg commit -S -q
453 HG: this is "commit.normal" template
453 HG: this is "commit.normal" template
454 HG: Leave message empty to abort commit.
454 HG: Leave message empty to abort commit.
455 HG: bookmark 'activebookmark' is activated
455 HG: bookmark 'activebookmark' is activated
456 HG: subrepo 'sub' is changed
456 HG: subrepo 'sub' is changed
457 HG: subrepo 'sub2' is changed
457 HG: subrepo 'sub2' is changed
458 abort: empty commit message
458 abort: empty commit message
459 [255]
459 [255]
460
460
461 $ cat >> .hg/hgrc <<EOF
461 $ cat >> .hg/hgrc <<EOF
462 > [committemplate]
462 > [committemplate]
463 > changeset.commit.normal =
463 > changeset.commit.normal =
464 > # now, "changeset.commit" should be chosen for "hg commit"
464 > # now, "changeset.commit" should be chosen for "hg commit"
465 > EOF
465 > EOF
466
466
467 $ hg bookmark --inactive activebookmark
467 $ hg bookmark --inactive activebookmark
468 $ hg forget .hgsub
468 $ hg forget .hgsub
469 $ HGEDITOR=cat hg commit -q
469 $ HGEDITOR=cat hg commit -q
470 HG: this is "commit" template
470 HG: this is "commit" template
471 HG: Leave message empty to abort commit.
471 HG: Leave message empty to abort commit.
472 HG: no bookmark is activated
472 HG: no bookmark is activated
473 abort: empty commit message
473 abort: empty commit message
474 [255]
474 [255]
475
475
476 $ cat >> .hg/hgrc <<EOF
476 $ cat >> .hg/hgrc <<EOF
477 > [committemplate]
477 > [committemplate]
478 > changeset.commit =
478 > changeset.commit =
479 > # now, "changeset" should be chosen for "hg commit"
479 > # now, "changeset" should be chosen for "hg commit"
480 > EOF
480 > EOF
481
481
482 $ HGEDITOR=cat hg commit -q
482 $ HGEDITOR=cat hg commit -q
483 HG: this is customized commit template
483 HG: this is customized commit template
484 HG: Leave message empty to abort commit.
484 HG: Leave message empty to abort commit.
485 HG: no bookmark is activated
485 HG: no bookmark is activated
486 abort: empty commit message
486 abort: empty commit message
487 [255]
487 [255]
488
488
489 $ cat >> .hg/hgrc <<EOF
489 $ cat >> .hg/hgrc <<EOF
490 > [committemplate]
490 > [committemplate]
491 > changeset = {desc}
491 > changeset = {desc}
492 > HG: mods={file_mods}
492 > HG: mods={file_mods}
493 > HG: adds={file_adds}
493 > HG: adds={file_adds}
494 > HG: dels={file_dels}
494 > HG: dels={file_dels}
495 > HG: files={files}
495 > HG: files={files}
496 > HG:
496 > HG:
497 > {splitlines(diff()) % 'HG: {line}\n'
497 > {splitlines(diff()) % 'HG: {line}\n'
498 > }HG:
498 > }HG:
499 > HG: mods={file_mods}
499 > HG: mods={file_mods}
500 > HG: adds={file_adds}
500 > HG: adds={file_adds}
501 > HG: dels={file_dels}
501 > HG: dels={file_dels}
502 > HG: files={files}\n
502 > HG: files={files}\n
503 > EOF
503 > EOF
504 $ hg status -amr
504 $ hg status -amr
505 M changed
505 M changed
506 A added
506 A added
507 R removed
507 R removed
508 $ HGEDITOR=cat hg commit -q -e -m "foo bar" changed
508 $ HGEDITOR=cat hg commit -q -e -m "foo bar" changed
509 foo bar
509 foo bar
510 HG: mods=changed
510 HG: mods=changed
511 HG: adds=
511 HG: adds=
512 HG: dels=
512 HG: dels=
513 HG: files=changed
513 HG: files=changed
514 HG:
514 HG:
515 HG: --- a/changed Thu Jan 01 00:00:00 1970 +0000
515 HG: --- a/changed Thu Jan 01 00:00:00 1970 +0000
516 HG: +++ b/changed Thu Jan 01 00:00:00 1970 +0000
516 HG: +++ b/changed Thu Jan 01 00:00:00 1970 +0000
517 HG: @@ -1,1 +1,2 @@
517 HG: @@ -1,1 +1,2 @@
518 HG: changed
518 HG: changed
519 HG: +changed
519 HG: +changed
520 HG:
520 HG:
521 HG: mods=changed
521 HG: mods=changed
522 HG: adds=
522 HG: adds=
523 HG: dels=
523 HG: dels=
524 HG: files=changed
524 HG: files=changed
525 $ hg status -amr
525 $ hg status -amr
526 A added
526 A added
527 R removed
527 R removed
528 $ hg parents --template "M {file_mods}\nA {file_adds}\nR {file_dels}\n"
528 $ hg parents --template "M {file_mods}\nA {file_adds}\nR {file_dels}\n"
529 M changed
529 M changed
530 A
530 A
531 R
531 R
532 $ hg rollback -q
532 $ hg rollback -q
533
533
534 $ cat >> .hg/hgrc <<EOF
534 $ cat >> .hg/hgrc <<EOF
535 > [committemplate]
535 > [committemplate]
536 > changeset = {desc}
536 > changeset = {desc}
537 > HG: mods={file_mods}
537 > HG: mods={file_mods}
538 > HG: adds={file_adds}
538 > HG: adds={file_adds}
539 > HG: dels={file_dels}
539 > HG: dels={file_dels}
540 > HG: files={files}
540 > HG: files={files}
541 > HG:
541 > HG:
542 > {splitlines(diff("changed")) % 'HG: {line}\n'
542 > {splitlines(diff("changed")) % 'HG: {line}\n'
543 > }HG:
543 > }HG:
544 > HG: mods={file_mods}
544 > HG: mods={file_mods}
545 > HG: adds={file_adds}
545 > HG: adds={file_adds}
546 > HG: dels={file_dels}
546 > HG: dels={file_dels}
547 > HG: files={files}
547 > HG: files={files}
548 > HG:
548 > HG:
549 > {splitlines(diff("added")) % 'HG: {line}\n'
549 > {splitlines(diff("added")) % 'HG: {line}\n'
550 > }HG:
550 > }HG:
551 > HG: mods={file_mods}
551 > HG: mods={file_mods}
552 > HG: adds={file_adds}
552 > HG: adds={file_adds}
553 > HG: dels={file_dels}
553 > HG: dels={file_dels}
554 > HG: files={files}
554 > HG: files={files}
555 > HG:
555 > HG:
556 > {splitlines(diff("removed")) % 'HG: {line}\n'
556 > {splitlines(diff("removed")) % 'HG: {line}\n'
557 > }HG:
557 > }HG:
558 > HG: mods={file_mods}
558 > HG: mods={file_mods}
559 > HG: adds={file_adds}
559 > HG: adds={file_adds}
560 > HG: dels={file_dels}
560 > HG: dels={file_dels}
561 > HG: files={files}\n
561 > HG: files={files}\n
562 > EOF
562 > EOF
563 $ HGEDITOR=cat hg commit -q -e -m "foo bar" added removed
563 $ HGEDITOR=cat hg commit -q -e -m "foo bar" added removed
564 foo bar
564 foo bar
565 HG: mods=
565 HG: mods=
566 HG: adds=added
566 HG: adds=added
567 HG: dels=removed
567 HG: dels=removed
568 HG: files=added removed
568 HG: files=added removed
569 HG:
569 HG:
570 HG:
570 HG:
571 HG: mods=
571 HG: mods=
572 HG: adds=added
572 HG: adds=added
573 HG: dels=removed
573 HG: dels=removed
574 HG: files=added removed
574 HG: files=added removed
575 HG:
575 HG:
576 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
576 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
577 HG: +++ b/added Thu Jan 01 00:00:00 1970 +0000
577 HG: +++ b/added Thu Jan 01 00:00:00 1970 +0000
578 HG: @@ -0,0 +1,1 @@
578 HG: @@ -0,0 +1,1 @@
579 HG: +added
579 HG: +added
580 HG:
580 HG:
581 HG: mods=
581 HG: mods=
582 HG: adds=added
582 HG: adds=added
583 HG: dels=removed
583 HG: dels=removed
584 HG: files=added removed
584 HG: files=added removed
585 HG:
585 HG:
586 HG: --- a/removed Thu Jan 01 00:00:00 1970 +0000
586 HG: --- a/removed Thu Jan 01 00:00:00 1970 +0000
587 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
587 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
588 HG: @@ -1,1 +0,0 @@
588 HG: @@ -1,1 +0,0 @@
589 HG: -removed
589 HG: -removed
590 HG:
590 HG:
591 HG: mods=
591 HG: mods=
592 HG: adds=added
592 HG: adds=added
593 HG: dels=removed
593 HG: dels=removed
594 HG: files=added removed
594 HG: files=added removed
595 $ hg status -amr
595 $ hg status -amr
596 M changed
596 M changed
597 $ hg parents --template "M {file_mods}\nA {file_adds}\nR {file_dels}\n"
597 $ hg parents --template "M {file_mods}\nA {file_adds}\nR {file_dels}\n"
598 M
598 M
599 A added
599 A added
600 R removed
600 R removed
601 $ hg rollback -q
601 $ hg rollback -q
602
602
603 $ cat >> .hg/hgrc <<EOF
603 $ cat >> .hg/hgrc <<EOF
604 > # disable customizing for subsequent tests
604 > # disable customizing for subsequent tests
605 > [committemplate]
605 > [committemplate]
606 > changeset =
606 > changeset =
607 > EOF
607 > EOF
608
608
609 $ cd ..
609 $ cd ..
610
610
611
611
612 commit copy
612 commit copy
613
613
614 $ hg init dir2
614 $ hg init dir2
615 $ cd dir2
615 $ cd dir2
616 $ echo bleh > bar
616 $ echo bleh > bar
617 $ hg add bar
617 $ hg add bar
618 $ hg ci -m 'add bar'
618 $ hg ci -m 'add bar'
619
619
620 $ hg cp bar foo
620 $ hg cp bar foo
621 $ echo >> bar
621 $ echo >> bar
622 $ hg ci -m 'cp bar foo; change bar'
622 $ hg ci -m 'cp bar foo; change bar'
623
623
624 $ hg debugrename foo
624 $ hg debugrename foo
625 foo renamed from bar:26d3ca0dfd18e44d796b564e38dd173c9668d3a9
625 foo renamed from bar:26d3ca0dfd18e44d796b564e38dd173c9668d3a9
626 $ hg debugindex bar
626 $ hg debugindex bar
627 rev offset length ..... linkrev nodeid p1 p2 (re)
627 rev offset length ..... linkrev nodeid p1 p2 (re)
628 0 0 6 ..... 0 26d3ca0dfd18 000000000000 000000000000 (re)
628 0 0 6 ..... 0 26d3ca0dfd18 000000000000 000000000000 (re)
629 1 6 7 ..... 1 d267bddd54f7 26d3ca0dfd18 000000000000 (re)
629 1 6 7 ..... 1 d267bddd54f7 26d3ca0dfd18 000000000000 (re)
630
630
631 Test making empty commits
631 Test making empty commits
632 $ hg commit --config ui.allowemptycommit=True -m "empty commit"
632 $ hg commit --config ui.allowemptycommit=True -m "empty commit"
633 $ hg log -r . -v --stat
633 $ hg log -r . -v --stat
634 changeset: 2:d809f3644287
634 changeset: 2:d809f3644287
635 tag: tip
635 tag: tip
636 user: test
636 user: test
637 date: Thu Jan 01 00:00:00 1970 +0000
637 date: Thu Jan 01 00:00:00 1970 +0000
638 description:
638 description:
639 empty commit
639 empty commit
640
640
641
641
642
642
643 verify pathauditor blocks evil filepaths
643 verify pathauditor blocks evil filepaths
644 $ cat > evil-commit.py <<EOF
644 $ cat > evil-commit.py <<EOF
645 > from mercurial import ui, hg, context, node
645 > from mercurial import ui, hg, context, node
646 > notrc = u".h\u200cg".encode('utf-8') + '/hgrc'
646 > notrc = u".h\u200cg".encode('utf-8') + '/hgrc'
647 > u = ui.ui.load()
647 > u = ui.ui.load()
648 > r = hg.repository(u, '.')
648 > r = hg.repository(u, '.')
649 > def filectxfn(repo, memctx, path):
649 > def filectxfn(repo, memctx, path):
650 > return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned')
650 > return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned')
651 > c = context.memctx(r, [r['tip'].node(), node.nullid],
651 > c = context.memctx(r, [r['tip'].node(), node.nullid],
652 > 'evil', [notrc], filectxfn, 0)
652 > 'evil', [notrc], filectxfn, 0)
653 > r.commitctx(c)
653 > r.commitctx(c)
654 > EOF
654 > EOF
655 $ $PYTHON evil-commit.py
655 $ $PYTHON evil-commit.py
656 #if windows
656 #if windows
657 $ hg co --clean tip
657 $ hg co --clean tip
658 abort: path contains illegal component: .h\xe2\x80\x8cg\\hgrc (esc)
658 abort: path contains illegal component: .h\xe2\x80\x8cg\\hgrc (esc)
659 [255]
659 [255]
660 #else
660 #else
661 $ hg co --clean tip
661 $ hg co --clean tip
662 abort: path contains illegal component: .h\xe2\x80\x8cg/hgrc (esc)
662 abort: path contains illegal component: .h\xe2\x80\x8cg/hgrc (esc)
663 [255]
663 [255]
664 #endif
664 #endif
665
665
666 $ hg rollback -f
666 $ hg rollback -f
667 repository tip rolled back to revision 2 (undo commit)
667 repository tip rolled back to revision 2 (undo commit)
668 $ cat > evil-commit.py <<EOF
668 $ cat > evil-commit.py <<EOF
669 > from mercurial import ui, hg, context, node
669 > from mercurial import ui, hg, context, node
670 > notrc = "HG~1/hgrc"
670 > notrc = "HG~1/hgrc"
671 > u = ui.ui.load()
671 > u = ui.ui.load()
672 > r = hg.repository(u, '.')
672 > r = hg.repository(u, '.')
673 > def filectxfn(repo, memctx, path):
673 > def filectxfn(repo, memctx, path):
674 > return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned')
674 > return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned')
675 > c = context.memctx(r, [r['tip'].node(), node.nullid],
675 > c = context.memctx(r, [r['tip'].node(), node.nullid],
676 > 'evil', [notrc], filectxfn, 0)
676 > 'evil', [notrc], filectxfn, 0)
677 > r.commitctx(c)
677 > r.commitctx(c)
678 > EOF
678 > EOF
679 $ $PYTHON evil-commit.py
679 $ $PYTHON evil-commit.py
680 $ hg co --clean tip
680 $ hg co --clean tip
681 abort: path contains illegal component: HG~1/hgrc (glob)
681 abort: path contains illegal component: HG~1/hgrc (glob)
682 [255]
682 [255]
683
683
684 $ hg rollback -f
684 $ hg rollback -f
685 repository tip rolled back to revision 2 (undo commit)
685 repository tip rolled back to revision 2 (undo commit)
686 $ cat > evil-commit.py <<EOF
686 $ cat > evil-commit.py <<EOF
687 > from mercurial import ui, hg, context, node
687 > from mercurial import ui, hg, context, node
688 > notrc = "HG8B6C~2/hgrc"
688 > notrc = "HG8B6C~2/hgrc"
689 > u = ui.ui.load()
689 > u = ui.ui.load()
690 > r = hg.repository(u, '.')
690 > r = hg.repository(u, '.')
691 > def filectxfn(repo, memctx, path):
691 > def filectxfn(repo, memctx, path):
692 > return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned')
692 > return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned')
693 > c = context.memctx(r, [r['tip'].node(), node.nullid],
693 > c = context.memctx(r, [r['tip'].node(), node.nullid],
694 > 'evil', [notrc], filectxfn, 0)
694 > 'evil', [notrc], filectxfn, 0)
695 > r.commitctx(c)
695 > r.commitctx(c)
696 > EOF
696 > EOF
697 $ $PYTHON evil-commit.py
697 $ $PYTHON evil-commit.py
698 $ hg co --clean tip
698 $ hg co --clean tip
699 abort: path contains illegal component: HG8B6C~2/hgrc (glob)
699 abort: path contains illegal component: HG8B6C~2/hgrc (glob)
700 [255]
700 [255]
701
701
702 # test that an unmodified commit template message aborts
702 # test that an unmodified commit template message aborts
703
703
704 $ hg init unmodified_commit_template
704 $ hg init unmodified_commit_template
705 $ cd unmodified_commit_template
705 $ cd unmodified_commit_template
706 $ echo foo > foo
706 $ echo foo > foo
707 $ hg add foo
707 $ hg add foo
708 $ hg commit -m "foo"
708 $ hg commit -m "foo"
709 $ cat >> .hg/hgrc <<EOF
709 $ cat >> .hg/hgrc <<EOF
710 > [committemplate]
710 > [committemplate]
711 > changeset.commit = HI THIS IS NOT STRIPPED
711 > changeset.commit = HI THIS IS NOT STRIPPED
712 > HG: this is customized commit template
712 > HG: this is customized commit template
713 > HG: {extramsg}
713 > HG: {extramsg}
714 > {if(activebookmark,
714 > {if(activebookmark,
715 > "HG: bookmark '{activebookmark}' is activated\n",
715 > "HG: bookmark '{activebookmark}' is activated\n",
716 > "HG: no bookmark is activated\n")}{subrepos %
716 > "HG: no bookmark is activated\n")}{subrepos %
717 > "HG: subrepo '{subrepo}' is changed\n"}
717 > "HG: subrepo '{subrepo}' is changed\n"}
718 > EOF
718 > EOF
719 $ cat > $TESTTMP/notouching.sh <<EOF
719 $ cat > $TESTTMP/notouching.sh <<EOF
720 > true
720 > true
721 > EOF
721 > EOF
722 $ echo foo2 > foo2
722 $ echo foo2 > foo2
723 $ hg add foo2
723 $ hg add foo2
724 $ HGEDITOR="sh $TESTTMP/notouching.sh" hg commit
724 $ HGEDITOR="sh $TESTTMP/notouching.sh" hg commit
725 abort: commit message unchanged
725 abort: commit message unchanged
726 [255]
726 [255]
727
727
728 test that text below the --- >8 --- special string is ignored
728 test that text below the --- >8 --- special string is ignored
729
729
730 $ cat <<'EOF' > $TESTTMP/lowercaseline.sh
730 $ cat <<'EOF' > $TESTTMP/lowercaseline.sh
731 > cat $1 | sed s/LINE/line/ | tee $1.new
731 > cat $1 | sed s/LINE/line/ | tee $1.new
732 > mv $1.new $1
732 > mv $1.new $1
733 > EOF
733 > EOF
734
734
735 $ hg init ignore_below_special_string
735 $ hg init ignore_below_special_string
736 $ cd ignore_below_special_string
736 $ cd ignore_below_special_string
737 $ echo foo > foo
737 $ echo foo > foo
738 $ hg add foo
738 $ hg add foo
739 $ hg commit -m "foo"
739 $ hg commit -m "foo"
740 $ cat >> .hg/hgrc <<EOF
740 $ cat >> .hg/hgrc <<EOF
741 > [committemplate]
741 > [committemplate]
742 > changeset.commit = first LINE
742 > changeset.commit = first LINE
743 > HG: this is customized commit template
743 > HG: this is customized commit template
744 > HG: {extramsg}
744 > HG: {extramsg}
745 > HG: ------------------------ >8 ------------------------
745 > HG: ------------------------ >8 ------------------------
746 > {diff()}
746 > {diff()}
747 > EOF
747 > EOF
748 $ echo foo2 > foo2
748 $ echo foo2 > foo2
749 $ hg add foo2
749 $ hg add foo2
750 $ HGEDITOR="sh $TESTTMP/notouching.sh" hg ci
750 $ HGEDITOR="sh $TESTTMP/notouching.sh" hg ci
751 abort: commit message unchanged
751 abort: commit message unchanged
752 [255]
752 [255]
753 $ HGEDITOR="sh $TESTTMP/lowercaseline.sh" hg ci
753 $ HGEDITOR="sh $TESTTMP/lowercaseline.sh" hg ci
754 first line
754 first line
755 HG: this is customized commit template
755 HG: this is customized commit template
756 HG: Leave message empty to abort commit.
756 HG: Leave message empty to abort commit.
757 HG: ------------------------ >8 ------------------------
757 HG: ------------------------ >8 ------------------------
758 diff -r e63c23eaa88a foo2
758 diff -r e63c23eaa88a foo2
759 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
759 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
760 +++ b/foo2 Thu Jan 01 00:00:00 1970 +0000
760 +++ b/foo2 Thu Jan 01 00:00:00 1970 +0000
761 @@ -0,0 +1,1 @@
761 @@ -0,0 +1,1 @@
762 +foo2
762 +foo2
763 $ hg log -T '{desc}\n' -r .
763 $ hg log -T '{desc}\n' -r .
764 first line
764 first line
765
765
766 test that the special string --- >8 --- isn't used when not at the beginning of
766 test that the special string --- >8 --- isn't used when not at the beginning of
767 a line
767 a line
768
768
769 $ cat >> .hg/hgrc <<EOF
769 $ cat >> .hg/hgrc <<EOF
770 > [committemplate]
770 > [committemplate]
771 > changeset.commit = first LINE2
771 > changeset.commit = first LINE2
772 > another line HG: ------------------------ >8 ------------------------
772 > another line HG: ------------------------ >8 ------------------------
773 > HG: this is customized commit template
773 > HG: this is customized commit template
774 > HG: {extramsg}
774 > HG: {extramsg}
775 > HG: ------------------------ >8 ------------------------
775 > HG: ------------------------ >8 ------------------------
776 > {diff()}
776 > {diff()}
777 > EOF
777 > EOF
778 $ echo foo >> foo
778 $ echo foo >> foo
779 $ HGEDITOR="sh $TESTTMP/lowercaseline.sh" hg ci
779 $ HGEDITOR="sh $TESTTMP/lowercaseline.sh" hg ci
780 first line2
780 first line2
781 another line HG: ------------------------ >8 ------------------------
781 another line HG: ------------------------ >8 ------------------------
782 HG: this is customized commit template
782 HG: this is customized commit template
783 HG: Leave message empty to abort commit.
783 HG: Leave message empty to abort commit.
784 HG: ------------------------ >8 ------------------------
784 HG: ------------------------ >8 ------------------------
785 diff -r 3661b22b0702 foo
785 diff -r 3661b22b0702 foo
786 --- a/foo Thu Jan 01 00:00:00 1970 +0000
786 --- a/foo Thu Jan 01 00:00:00 1970 +0000
787 +++ b/foo Thu Jan 01 00:00:00 1970 +0000
787 +++ b/foo Thu Jan 01 00:00:00 1970 +0000
788 @@ -1,1 +1,2 @@
788 @@ -1,1 +1,2 @@
789 foo
789 foo
790 +foo
790 +foo
791 $ hg log -T '{desc}\n' -r .
791 $ hg log -T '{desc}\n' -r .
792 first line2
792 first line2
793 another line HG: ------------------------ >8 ------------------------
793 another line HG: ------------------------ >8 ------------------------
794
794
795 also test that this special string isn't accepted when there is some extra text
795 also test that this special string isn't accepted when there is some extra text
796 at the end
796 at the end
797
797
798 $ cat >> .hg/hgrc <<EOF
798 $ cat >> .hg/hgrc <<EOF
799 > [committemplate]
799 > [committemplate]
800 > changeset.commit = first LINE3
800 > changeset.commit = first LINE3
801 > HG: ------------------------ >8 ------------------------foobar
801 > HG: ------------------------ >8 ------------------------foobar
802 > second line
802 > second line
803 > HG: this is customized commit template
803 > HG: this is customized commit template
804 > HG: {extramsg}
804 > HG: {extramsg}
805 > HG: ------------------------ >8 ------------------------
805 > HG: ------------------------ >8 ------------------------
806 > {diff()}
806 > {diff()}
807 > EOF
807 > EOF
808 $ echo foo >> foo
808 $ echo foo >> foo
809 $ HGEDITOR="sh $TESTTMP/lowercaseline.sh" hg ci
809 $ HGEDITOR="sh $TESTTMP/lowercaseline.sh" hg ci
810 first line3
810 first line3
811 HG: ------------------------ >8 ------------------------foobar
811 HG: ------------------------ >8 ------------------------foobar
812 second line
812 second line
813 HG: this is customized commit template
813 HG: this is customized commit template
814 HG: Leave message empty to abort commit.
814 HG: Leave message empty to abort commit.
815 HG: ------------------------ >8 ------------------------
815 HG: ------------------------ >8 ------------------------
816 diff -r ce648f5f066f foo
816 diff -r ce648f5f066f foo
817 --- a/foo Thu Jan 01 00:00:00 1970 +0000
817 --- a/foo Thu Jan 01 00:00:00 1970 +0000
818 +++ b/foo Thu Jan 01 00:00:00 1970 +0000
818 +++ b/foo Thu Jan 01 00:00:00 1970 +0000
819 @@ -1,2 +1,3 @@
819 @@ -1,2 +1,3 @@
820 foo
820 foo
821 foo
821 foo
822 +foo
822 +foo
823 $ hg log -T '{desc}\n' -r .
823 $ hg log -T '{desc}\n' -r .
824 first line3
824 first line3
825 second line
825 second line
826
826
827 $ cd ..
827 $ cd ..
828
828
@@ -1,250 +1,249 b''
1 # Create server
1 # Create server
2 $ hg init server
2 $ hg init server
3 $ cd server
3 $ cd server
4 $ cat >> .hg/hgrc << EOF
4 $ cat >> .hg/hgrc << EOF
5 > [extensions]
5 > [extensions]
6 > extension=$TESTDIR/flagprocessorext.py
6 > extension=$TESTDIR/flagprocessorext.py
7 > EOF
7 > EOF
8 $ cd ../
8 $ cd ../
9
9
10 # Clone server and enable extensions
10 # Clone server and enable extensions
11 $ hg clone -q server client
11 $ hg clone -q server client
12 $ cd client
12 $ cd client
13 $ cat >> .hg/hgrc << EOF
13 $ cat >> .hg/hgrc << EOF
14 > [extensions]
14 > [extensions]
15 > extension=$TESTDIR/flagprocessorext.py
15 > extension=$TESTDIR/flagprocessorext.py
16 > EOF
16 > EOF
17
17
18 # Commit file that will trigger the noop extension
18 # Commit file that will trigger the noop extension
19 $ echo '[NOOP]' > noop
19 $ echo '[NOOP]' > noop
20 $ hg commit -Aqm "noop"
20 $ hg commit -Aqm "noop"
21
21
22 # Commit file that will trigger the base64 extension
22 # Commit file that will trigger the base64 extension
23 $ echo '[BASE64]' > base64
23 $ echo '[BASE64]' > base64
24 $ hg commit -Aqm 'base64'
24 $ hg commit -Aqm 'base64'
25
25
26 # Commit file that will trigger the gzip extension
26 # Commit file that will trigger the gzip extension
27 $ echo '[GZIP]' > gzip
27 $ echo '[GZIP]' > gzip
28 $ hg commit -Aqm 'gzip'
28 $ hg commit -Aqm 'gzip'
29
29
30 # Commit file that will trigger noop and base64
30 # Commit file that will trigger noop and base64
31 $ echo '[NOOP][BASE64]' > noop-base64
31 $ echo '[NOOP][BASE64]' > noop-base64
32 $ hg commit -Aqm 'noop+base64'
32 $ hg commit -Aqm 'noop+base64'
33
33
34 # Commit file that will trigger noop and gzip
34 # Commit file that will trigger noop and gzip
35 $ echo '[NOOP][GZIP]' > noop-gzip
35 $ echo '[NOOP][GZIP]' > noop-gzip
36 $ hg commit -Aqm 'noop+gzip'
36 $ hg commit -Aqm 'noop+gzip'
37
37
38 # Commit file that will trigger base64 and gzip
38 # Commit file that will trigger base64 and gzip
39 $ echo '[BASE64][GZIP]' > base64-gzip
39 $ echo '[BASE64][GZIP]' > base64-gzip
40 $ hg commit -Aqm 'base64+gzip'
40 $ hg commit -Aqm 'base64+gzip'
41
41
42 # Commit file that will trigger base64, gzip and noop
42 # Commit file that will trigger base64, gzip and noop
43 $ echo '[BASE64][GZIP][NOOP]' > base64-gzip-noop
43 $ echo '[BASE64][GZIP][NOOP]' > base64-gzip-noop
44 $ hg commit -Aqm 'base64+gzip+noop'
44 $ hg commit -Aqm 'base64+gzip+noop'
45
45
46 # TEST: ensure the revision data is consistent
46 # TEST: ensure the revision data is consistent
47 $ hg cat noop
47 $ hg cat noop
48 [NOOP]
48 [NOOP]
49 $ hg debugdata noop 0
49 $ hg debugdata noop 0
50 [NOOP]
50 [NOOP]
51
51
52 $ hg cat -r . base64
52 $ hg cat -r . base64
53 [BASE64]
53 [BASE64]
54 $ hg debugdata base64 0
54 $ hg debugdata base64 0
55 W0JBU0U2NF0K (no-eol)
55 W0JBU0U2NF0K (no-eol)
56
56
57 $ hg cat -r . gzip
57 $ hg cat -r . gzip
58 [GZIP]
58 [GZIP]
59 $ hg debugdata gzip 0
59 $ hg debugdata gzip 0
60 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
60 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
61
61
62 $ hg cat -r . noop-base64
62 $ hg cat -r . noop-base64
63 [NOOP][BASE64]
63 [NOOP][BASE64]
64 $ hg debugdata noop-base64 0
64 $ hg debugdata noop-base64 0
65 W05PT1BdW0JBU0U2NF0K (no-eol)
65 W05PT1BdW0JBU0U2NF0K (no-eol)
66
66
67 $ hg cat -r . noop-gzip
67 $ hg cat -r . noop-gzip
68 [NOOP][GZIP]
68 [NOOP][GZIP]
69 $ hg debugdata noop-gzip 0
69 $ hg debugdata noop-gzip 0
70 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
70 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
71
71
72 $ hg cat -r . base64-gzip
72 $ hg cat -r . base64-gzip
73 [BASE64][GZIP]
73 [BASE64][GZIP]
74 $ hg debugdata base64-gzip 0
74 $ hg debugdata base64-gzip 0
75 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
75 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
76
76
77 $ hg cat -r . base64-gzip-noop
77 $ hg cat -r . base64-gzip-noop
78 [BASE64][GZIP][NOOP]
78 [BASE64][GZIP][NOOP]
79 $ hg debugdata base64-gzip-noop 0
79 $ hg debugdata base64-gzip-noop 0
80 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
80 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
81
81
82 # Push to the server
82 # Push to the server
83 $ hg push
83 $ hg push
84 pushing to $TESTTMP/server (glob)
84 pushing to $TESTTMP/server (glob)
85 searching for changes
85 searching for changes
86 adding changesets
86 adding changesets
87 adding manifests
87 adding manifests
88 adding file changes
88 adding file changes
89 added 7 changesets with 7 changes to 7 files
89 added 7 changesets with 7 changes to 7 files
90
90
91 # Initialize new client (not cloning) and setup extension
91 # Initialize new client (not cloning) and setup extension
92 $ cd ..
92 $ cd ..
93 $ hg init client2
93 $ hg init client2
94 $ cd client2
94 $ cd client2
95 $ cat >> .hg/hgrc << EOF
95 $ cat >> .hg/hgrc << EOF
96 > [paths]
96 > [paths]
97 > default = $TESTTMP/server
97 > default = $TESTTMP/server
98 > [extensions]
98 > [extensions]
99 > extension=$TESTDIR/flagprocessorext.py
99 > extension=$TESTDIR/flagprocessorext.py
100 > EOF
100 > EOF
101
101
102 # Pull from server and update to latest revision
102 # Pull from server and update to latest revision
103 $ hg pull default
103 $ hg pull default
104 pulling from $TESTTMP/server (glob)
104 pulling from $TESTTMP/server (glob)
105 requesting all changes
105 requesting all changes
106 adding changesets
106 adding changesets
107 adding manifests
107 adding manifests
108 adding file changes
108 adding file changes
109 added 7 changesets with 7 changes to 7 files
109 added 7 changesets with 7 changes to 7 files
110 (run 'hg update' to get a working copy)
110 (run 'hg update' to get a working copy)
111 $ hg update
111 $ hg update
112 7 files updated, 0 files merged, 0 files removed, 0 files unresolved
112 7 files updated, 0 files merged, 0 files removed, 0 files unresolved
113
113
114 # TEST: ensure the revision data is consistent
114 # TEST: ensure the revision data is consistent
115 $ hg cat noop
115 $ hg cat noop
116 [NOOP]
116 [NOOP]
117 $ hg debugdata noop 0
117 $ hg debugdata noop 0
118 [NOOP]
118 [NOOP]
119
119
120 $ hg cat -r . base64
120 $ hg cat -r . base64
121 [BASE64]
121 [BASE64]
122 $ hg debugdata base64 0
122 $ hg debugdata base64 0
123 W0JBU0U2NF0K (no-eol)
123 W0JBU0U2NF0K (no-eol)
124
124
125 $ hg cat -r . gzip
125 $ hg cat -r . gzip
126 [GZIP]
126 [GZIP]
127 $ hg debugdata gzip 0
127 $ hg debugdata gzip 0
128 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
128 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
129
129
130 $ hg cat -r . noop-base64
130 $ hg cat -r . noop-base64
131 [NOOP][BASE64]
131 [NOOP][BASE64]
132 $ hg debugdata noop-base64 0
132 $ hg debugdata noop-base64 0
133 W05PT1BdW0JBU0U2NF0K (no-eol)
133 W05PT1BdW0JBU0U2NF0K (no-eol)
134
134
135 $ hg cat -r . noop-gzip
135 $ hg cat -r . noop-gzip
136 [NOOP][GZIP]
136 [NOOP][GZIP]
137 $ hg debugdata noop-gzip 0
137 $ hg debugdata noop-gzip 0
138 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
138 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
139
139
140 $ hg cat -r . base64-gzip
140 $ hg cat -r . base64-gzip
141 [BASE64][GZIP]
141 [BASE64][GZIP]
142 $ hg debugdata base64-gzip 0
142 $ hg debugdata base64-gzip 0
143 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
143 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
144
144
145 $ hg cat -r . base64-gzip-noop
145 $ hg cat -r . base64-gzip-noop
146 [BASE64][GZIP][NOOP]
146 [BASE64][GZIP][NOOP]
147 $ hg debugdata base64-gzip-noop 0
147 $ hg debugdata base64-gzip-noop 0
148 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
148 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
149
149
150 # TEST: ensure a missing processor is handled
150 # TEST: ensure a missing processor is handled
151 $ echo '[FAIL][BASE64][GZIP][NOOP]' > fail-base64-gzip-noop
151 $ echo '[FAIL][BASE64][GZIP][NOOP]' > fail-base64-gzip-noop
152 $ hg commit -Aqm 'fail+base64+gzip+noop'
152 $ hg commit -Aqm 'fail+base64+gzip+noop'
153 abort: missing processor for flag '0x1'!
153 abort: missing processor for flag '0x1'!
154 [255]
154 [255]
155 $ hg forget fail-base64-gzip-noop
156 $ rm fail-base64-gzip-noop
155 $ rm fail-base64-gzip-noop
157
156
158 # TEST: ensure we cannot register several flag processors on the same flag
157 # TEST: ensure we cannot register several flag processors on the same flag
159 $ cat >> .hg/hgrc << EOF
158 $ cat >> .hg/hgrc << EOF
160 > [extensions]
159 > [extensions]
161 > extension=$TESTDIR/flagprocessorext.py
160 > extension=$TESTDIR/flagprocessorext.py
162 > duplicate=$TESTDIR/flagprocessorext.py
161 > duplicate=$TESTDIR/flagprocessorext.py
163 > EOF
162 > EOF
164 $ hg debugrebuilddirstate
163 $ hg debugrebuilddirstate
165 *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
164 *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
166 $ hg st 2>&1 | egrep 'cannot register multiple processors|flagprocessorext'
165 $ hg st 2>&1 | egrep 'cannot register multiple processors|flagprocessorext'
167 *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
166 *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
168 File "*/tests/flagprocessorext.py", line *, in b64decode (glob)
167 File "*/tests/flagprocessorext.py", line *, in b64decode (glob)
169
168
170 $ cd ..
169 $ cd ..
171
170
172 # TEST: bundle repo
171 # TEST: bundle repo
173 $ hg init bundletest
172 $ hg init bundletest
174 $ cd bundletest
173 $ cd bundletest
175
174
176 $ cat >> .hg/hgrc << EOF
175 $ cat >> .hg/hgrc << EOF
177 > [extensions]
176 > [extensions]
178 > flagprocessor=$TESTDIR/flagprocessorext.py
177 > flagprocessor=$TESTDIR/flagprocessorext.py
179 > EOF
178 > EOF
180
179
181 $ for i in 0 single two three 4; do
180 $ for i in 0 single two three 4; do
182 > echo '[BASE64]a-bit-longer-'$i > base64
181 > echo '[BASE64]a-bit-longer-'$i > base64
183 > hg commit -m base64-$i -A base64
182 > hg commit -m base64-$i -A base64
184 > done
183 > done
185
184
186 $ hg update 2 -q
185 $ hg update 2 -q
187 $ echo '[BASE64]a-bit-longer-branching' > base64
186 $ echo '[BASE64]a-bit-longer-branching' > base64
188 $ hg commit -q -m branching
187 $ hg commit -q -m branching
189
188
190 $ hg bundle --base 1 bundle.hg
189 $ hg bundle --base 1 bundle.hg
191 4 changesets found
190 4 changesets found
192 $ hg --config extensions.strip= strip -r 2 --no-backup --force -q
191 $ hg --config extensions.strip= strip -r 2 --no-backup --force -q
193 $ hg -R bundle.hg log --stat -T '{rev} {desc}\n' base64
192 $ hg -R bundle.hg log --stat -T '{rev} {desc}\n' base64
194 5 branching
193 5 branching
195 base64 | 2 +-
194 base64 | 2 +-
196 1 files changed, 1 insertions(+), 1 deletions(-)
195 1 files changed, 1 insertions(+), 1 deletions(-)
197
196
198 4 base64-4
197 4 base64-4
199 base64 | 2 +-
198 base64 | 2 +-
200 1 files changed, 1 insertions(+), 1 deletions(-)
199 1 files changed, 1 insertions(+), 1 deletions(-)
201
200
202 3 base64-three
201 3 base64-three
203 base64 | 2 +-
202 base64 | 2 +-
204 1 files changed, 1 insertions(+), 1 deletions(-)
203 1 files changed, 1 insertions(+), 1 deletions(-)
205
204
206 2 base64-two
205 2 base64-two
207 base64 | 2 +-
206 base64 | 2 +-
208 1 files changed, 1 insertions(+), 1 deletions(-)
207 1 files changed, 1 insertions(+), 1 deletions(-)
209
208
210 1 base64-single
209 1 base64-single
211 base64 | 2 +-
210 base64 | 2 +-
212 1 files changed, 1 insertions(+), 1 deletions(-)
211 1 files changed, 1 insertions(+), 1 deletions(-)
213
212
214 0 base64-0
213 0 base64-0
215 base64 | 1 +
214 base64 | 1 +
216 1 files changed, 1 insertions(+), 0 deletions(-)
215 1 files changed, 1 insertions(+), 0 deletions(-)
217
216
218
217
219 $ hg bundle -R bundle.hg --base 1 bundle-again.hg -q
218 $ hg bundle -R bundle.hg --base 1 bundle-again.hg -q
220 $ hg -R bundle-again.hg log --stat -T '{rev} {desc}\n' base64
219 $ hg -R bundle-again.hg log --stat -T '{rev} {desc}\n' base64
221 5 branching
220 5 branching
222 base64 | 2 +-
221 base64 | 2 +-
223 1 files changed, 1 insertions(+), 1 deletions(-)
222 1 files changed, 1 insertions(+), 1 deletions(-)
224
223
225 4 base64-4
224 4 base64-4
226 base64 | 2 +-
225 base64 | 2 +-
227 1 files changed, 1 insertions(+), 1 deletions(-)
226 1 files changed, 1 insertions(+), 1 deletions(-)
228
227
229 3 base64-three
228 3 base64-three
230 base64 | 2 +-
229 base64 | 2 +-
231 1 files changed, 1 insertions(+), 1 deletions(-)
230 1 files changed, 1 insertions(+), 1 deletions(-)
232
231
233 2 base64-two
232 2 base64-two
234 base64 | 2 +-
233 base64 | 2 +-
235 1 files changed, 1 insertions(+), 1 deletions(-)
234 1 files changed, 1 insertions(+), 1 deletions(-)
236
235
237 1 base64-single
236 1 base64-single
238 base64 | 2 +-
237 base64 | 2 +-
239 1 files changed, 1 insertions(+), 1 deletions(-)
238 1 files changed, 1 insertions(+), 1 deletions(-)
240
239
241 0 base64-0
240 0 base64-0
242 base64 | 1 +
241 base64 | 1 +
243 1 files changed, 1 insertions(+), 0 deletions(-)
242 1 files changed, 1 insertions(+), 0 deletions(-)
244
243
245 $ rm bundle.hg bundle-again.hg
244 $ rm bundle.hg bundle-again.hg
246
245
247 # TEST: hg status
246 # TEST: hg status
248
247
249 $ hg status
248 $ hg status
250 $ hg diff
249 $ hg diff
@@ -1,25 +1,27 b''
1 $ hg init
1 $ hg init
2 $ touch a
2 $ touch a
3
3
4 $ unset HGUSER
4 $ unset HGUSER
5 $ echo "[ui]" >> .hg/hgrc
5 $ echo "[ui]" >> .hg/hgrc
6 $ echo "username= foo" >> .hg/hgrc
6 $ echo "username= foo" >> .hg/hgrc
7 $ echo " bar1" >> .hg/hgrc
7 $ echo " bar1" >> .hg/hgrc
8
8
9 $ hg ci -Am m
9 $ hg ci -Am m
10 adding a
10 adding a
11 abort: username 'foo\nbar1' contains a newline
11 abort: username 'foo\nbar1' contains a newline
12
12
13 [255]
13 [255]
14 $ rm .hg/hgrc
14 $ rm .hg/hgrc
15
15
16 $ HGUSER=`(echo foo; echo bar2)` hg ci -Am m
16 $ HGUSER=`(echo foo; echo bar2)` hg ci -Am m
17 adding a
17 abort: username 'foo\nbar2' contains a newline
18 abort: username 'foo\nbar2' contains a newline
18
19
19 [255]
20 [255]
20 $ hg ci -Am m -u "`(echo foo; echo bar3)`"
21 $ hg ci -Am m -u "`(echo foo; echo bar3)`"
22 adding a
21 transaction abort!
23 transaction abort!
22 rollback completed
24 rollback completed
23 abort: username 'foo\nbar3' contains a newline!
25 abort: username 'foo\nbar3' contains a newline!
24 [255]
26 [255]
25
27
General Comments 0
You need to be logged in to leave comments. Login now