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