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