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