##// END OF EJS Templates
keyword: replace use of _filerev with _filenode...
Christian Ebert -
r29055:4a65c9c6 default
parent child Browse files
Show More
@@ -1,754 +1,754
1 # keyword.py - $Keyword$ expansion for Mercurial
1 # keyword.py - $Keyword$ expansion for Mercurial
2 #
2 #
3 # Copyright 2007-2015 Christian Ebert <blacktrash@gmx.net>
3 # Copyright 2007-2015 Christian Ebert <blacktrash@gmx.net>
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 # $Id$
8 # $Id$
9 #
9 #
10 # Keyword expansion hack against the grain of a Distributed SCM
10 # Keyword expansion hack against the grain of a Distributed SCM
11 #
11 #
12 # There are many good reasons why this is not needed in a distributed
12 # There are many good reasons why this is not needed in a distributed
13 # SCM, still it may be useful in very small projects based on single
13 # SCM, still it may be useful in very small projects based on single
14 # files (like LaTeX packages), that are mostly addressed to an
14 # files (like LaTeX packages), that are mostly addressed to an
15 # audience not running a version control system.
15 # audience not running a version control system.
16 #
16 #
17 # For in-depth discussion refer to
17 # For in-depth discussion refer to
18 # <https://mercurial-scm.org/wiki/KeywordPlan>.
18 # <https://mercurial-scm.org/wiki/KeywordPlan>.
19 #
19 #
20 # Keyword expansion is based on Mercurial's changeset template mappings.
20 # Keyword expansion is based on Mercurial's changeset template mappings.
21 #
21 #
22 # Binary files are not touched.
22 # Binary files are not touched.
23 #
23 #
24 # Files to act upon/ignore are specified in the [keyword] section.
24 # Files to act upon/ignore are specified in the [keyword] section.
25 # Customized keyword template mappings in the [keywordmaps] section.
25 # Customized keyword template mappings in the [keywordmaps] section.
26 #
26 #
27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
28
28
29 '''expand keywords in tracked files
29 '''expand keywords in tracked files
30
30
31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
32 tracked text files selected by your configuration.
32 tracked text files selected by your configuration.
33
33
34 Keywords are only expanded in local repositories and not stored in the
34 Keywords are only expanded in local repositories and not stored in the
35 change history. The mechanism can be regarded as a convenience for the
35 change history. The mechanism can be regarded as a convenience for the
36 current user or for archive distribution.
36 current user or for archive distribution.
37
37
38 Keywords expand to the changeset data pertaining to the latest change
38 Keywords expand to the changeset data pertaining to the latest change
39 relative to the working directory parent of each file.
39 relative to the working directory parent of each file.
40
40
41 Configuration is done in the [keyword], [keywordset] and [keywordmaps]
41 Configuration is done in the [keyword], [keywordset] and [keywordmaps]
42 sections of hgrc files.
42 sections of hgrc files.
43
43
44 Example::
44 Example::
45
45
46 [keyword]
46 [keyword]
47 # expand keywords in every python file except those matching "x*"
47 # expand keywords in every python file except those matching "x*"
48 **.py =
48 **.py =
49 x* = ignore
49 x* = ignore
50
50
51 [keywordset]
51 [keywordset]
52 # prefer svn- over cvs-like default keywordmaps
52 # prefer svn- over cvs-like default keywordmaps
53 svn = True
53 svn = True
54
54
55 .. note::
55 .. note::
56
56
57 The more specific you are in your filename patterns the less you
57 The more specific you are in your filename patterns the less you
58 lose speed in huge repositories.
58 lose speed in huge repositories.
59
59
60 For [keywordmaps] template mapping and expansion demonstration and
60 For [keywordmaps] template mapping and expansion demonstration and
61 control run :hg:`kwdemo`. See :hg:`help templates` for a list of
61 control run :hg:`kwdemo`. See :hg:`help templates` for a list of
62 available templates and filters.
62 available templates and filters.
63
63
64 Three additional date template filters are provided:
64 Three additional date template filters are provided:
65
65
66 :``utcdate``: "2006/09/18 15:13:13"
66 :``utcdate``: "2006/09/18 15:13:13"
67 :``svnutcdate``: "2006-09-18 15:13:13Z"
67 :``svnutcdate``: "2006-09-18 15:13:13Z"
68 :``svnisodate``: "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
68 :``svnisodate``: "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
69
69
70 The default template mappings (view with :hg:`kwdemo -d`) can be
70 The default template mappings (view with :hg:`kwdemo -d`) can be
71 replaced with customized keywords and templates. Again, run
71 replaced with customized keywords and templates. Again, run
72 :hg:`kwdemo` to control the results of your configuration changes.
72 :hg:`kwdemo` to control the results of your configuration changes.
73
73
74 Before changing/disabling active keywords, you must run :hg:`kwshrink`
74 Before changing/disabling active keywords, you must run :hg:`kwshrink`
75 to avoid storing expanded keywords in the change history.
75 to avoid storing expanded keywords in the change history.
76
76
77 To force expansion after enabling it, or a configuration change, run
77 To force expansion after enabling it, or a configuration change, run
78 :hg:`kwexpand`.
78 :hg:`kwexpand`.
79
79
80 Expansions spanning more than one line and incremental expansions,
80 Expansions spanning more than one line and incremental expansions,
81 like CVS' $Log$, are not supported. A keyword template map "Log =
81 like CVS' $Log$, are not supported. A keyword template map "Log =
82 {desc}" expands to the first line of the changeset description.
82 {desc}" expands to the first line of the changeset description.
83 '''
83 '''
84
84
85
85
86 from __future__ import absolute_import
86 from __future__ import absolute_import
87
87
88 import os
88 import os
89 import re
89 import re
90 import tempfile
90 import tempfile
91
91
92 from mercurial.hgweb import webcommands
92 from mercurial.hgweb import webcommands
93 from mercurial.i18n import _
93 from mercurial.i18n import _
94
94
95 from mercurial import (
95 from mercurial import (
96 cmdutil,
96 cmdutil,
97 commands,
97 commands,
98 context,
98 context,
99 dispatch,
99 dispatch,
100 error,
100 error,
101 extensions,
101 extensions,
102 filelog,
102 filelog,
103 localrepo,
103 localrepo,
104 match,
104 match,
105 patch,
105 patch,
106 pathutil,
106 pathutil,
107 registrar,
107 registrar,
108 scmutil,
108 scmutil,
109 templatefilters,
109 templatefilters,
110 util,
110 util,
111 )
111 )
112
112
113 cmdtable = {}
113 cmdtable = {}
114 command = cmdutil.command(cmdtable)
114 command = cmdutil.command(cmdtable)
115 # Note for extension authors: ONLY specify testedwith = 'internal' for
115 # Note for extension authors: ONLY specify testedwith = 'internal' for
116 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
116 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
117 # be specifying the version(s) of Mercurial they are tested with, or
117 # be specifying the version(s) of Mercurial they are tested with, or
118 # leave the attribute unspecified.
118 # leave the attribute unspecified.
119 testedwith = 'internal'
119 testedwith = 'internal'
120
120
121 # hg commands that do not act on keywords
121 # hg commands that do not act on keywords
122 nokwcommands = ('add addremove annotate bundle export grep incoming init log'
122 nokwcommands = ('add addremove annotate bundle export grep incoming init log'
123 ' outgoing push tip verify convert email glog')
123 ' outgoing push tip verify convert email glog')
124
124
125 # hg commands that trigger expansion only when writing to working dir,
125 # hg commands that trigger expansion only when writing to working dir,
126 # not when reading filelog, and unexpand when reading from working dir
126 # not when reading filelog, and unexpand when reading from working dir
127 restricted = ('merge kwexpand kwshrink record qrecord resolve transplant'
127 restricted = ('merge kwexpand kwshrink record qrecord resolve transplant'
128 ' unshelve rebase graft backout histedit fetch')
128 ' unshelve rebase graft backout histedit fetch')
129
129
130 # names of extensions using dorecord
130 # names of extensions using dorecord
131 recordextensions = 'record'
131 recordextensions = 'record'
132
132
133 colortable = {
133 colortable = {
134 'kwfiles.enabled': 'green bold',
134 'kwfiles.enabled': 'green bold',
135 'kwfiles.deleted': 'cyan bold underline',
135 'kwfiles.deleted': 'cyan bold underline',
136 'kwfiles.enabledunknown': 'green',
136 'kwfiles.enabledunknown': 'green',
137 'kwfiles.ignored': 'bold',
137 'kwfiles.ignored': 'bold',
138 'kwfiles.ignoredunknown': 'none'
138 'kwfiles.ignoredunknown': 'none'
139 }
139 }
140
140
141 templatefilter = registrar.templatefilter()
141 templatefilter = registrar.templatefilter()
142
142
143 # date like in cvs' $Date
143 # date like in cvs' $Date
144 @templatefilter('utcdate')
144 @templatefilter('utcdate')
145 def utcdate(text):
145 def utcdate(text):
146 '''Date. Returns a UTC-date in this format: "2009/08/18 11:00:13".
146 '''Date. Returns a UTC-date in this format: "2009/08/18 11:00:13".
147 '''
147 '''
148 return util.datestr((util.parsedate(text)[0], 0), '%Y/%m/%d %H:%M:%S')
148 return util.datestr((util.parsedate(text)[0], 0), '%Y/%m/%d %H:%M:%S')
149 # date like in svn's $Date
149 # date like in svn's $Date
150 @templatefilter('svnisodate')
150 @templatefilter('svnisodate')
151 def svnisodate(text):
151 def svnisodate(text):
152 '''Date. Returns a date in this format: "2009-08-18 13:00:13
152 '''Date. Returns a date in this format: "2009-08-18 13:00:13
153 +0200 (Tue, 18 Aug 2009)".
153 +0200 (Tue, 18 Aug 2009)".
154 '''
154 '''
155 return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
155 return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
156 # date like in svn's $Id
156 # date like in svn's $Id
157 @templatefilter('svnutcdate')
157 @templatefilter('svnutcdate')
158 def svnutcdate(text):
158 def svnutcdate(text):
159 '''Date. Returns a UTC-date in this format: "2009-08-18
159 '''Date. Returns a UTC-date in this format: "2009-08-18
160 11:00:13Z".
160 11:00:13Z".
161 '''
161 '''
162 return util.datestr((util.parsedate(text)[0], 0), '%Y-%m-%d %H:%M:%SZ')
162 return util.datestr((util.parsedate(text)[0], 0), '%Y-%m-%d %H:%M:%SZ')
163
163
164 # make keyword tools accessible
164 # make keyword tools accessible
165 kwtools = {'templater': None, 'hgcmd': ''}
165 kwtools = {'templater': None, 'hgcmd': ''}
166
166
167 def _defaultkwmaps(ui):
167 def _defaultkwmaps(ui):
168 '''Returns default keywordmaps according to keywordset configuration.'''
168 '''Returns default keywordmaps according to keywordset configuration.'''
169 templates = {
169 templates = {
170 'Revision': '{node|short}',
170 'Revision': '{node|short}',
171 'Author': '{author|user}',
171 'Author': '{author|user}',
172 }
172 }
173 kwsets = ({
173 kwsets = ({
174 'Date': '{date|utcdate}',
174 'Date': '{date|utcdate}',
175 'RCSfile': '{file|basename},v',
175 'RCSfile': '{file|basename},v',
176 'RCSFile': '{file|basename},v', # kept for backwards compatibility
176 'RCSFile': '{file|basename},v', # kept for backwards compatibility
177 # with hg-keyword
177 # with hg-keyword
178 'Source': '{root}/{file},v',
178 'Source': '{root}/{file},v',
179 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
179 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
180 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
180 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
181 }, {
181 }, {
182 'Date': '{date|svnisodate}',
182 'Date': '{date|svnisodate}',
183 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
183 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
184 'LastChangedRevision': '{node|short}',
184 'LastChangedRevision': '{node|short}',
185 'LastChangedBy': '{author|user}',
185 'LastChangedBy': '{author|user}',
186 'LastChangedDate': '{date|svnisodate}',
186 'LastChangedDate': '{date|svnisodate}',
187 })
187 })
188 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
188 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
189 return templates
189 return templates
190
190
191 def _shrinktext(text, subfunc):
191 def _shrinktext(text, subfunc):
192 '''Helper for keyword expansion removal in text.
192 '''Helper for keyword expansion removal in text.
193 Depending on subfunc also returns number of substitutions.'''
193 Depending on subfunc also returns number of substitutions.'''
194 return subfunc(r'$\1$', text)
194 return subfunc(r'$\1$', text)
195
195
196 def _preselect(wstatus, changed):
196 def _preselect(wstatus, changed):
197 '''Retrieves modified and added files from a working directory state
197 '''Retrieves modified and added files from a working directory state
198 and returns the subset of each contained in given changed files
198 and returns the subset of each contained in given changed files
199 retrieved from a change context.'''
199 retrieved from a change context.'''
200 modified = [f for f in wstatus.modified if f in changed]
200 modified = [f for f in wstatus.modified if f in changed]
201 added = [f for f in wstatus.added if f in changed]
201 added = [f for f in wstatus.added if f in changed]
202 return modified, added
202 return modified, added
203
203
204
204
205 class kwtemplater(object):
205 class kwtemplater(object):
206 '''
206 '''
207 Sets up keyword templates, corresponding keyword regex, and
207 Sets up keyword templates, corresponding keyword regex, and
208 provides keyword substitution functions.
208 provides keyword substitution functions.
209 '''
209 '''
210
210
211 def __init__(self, ui, repo, inc, exc):
211 def __init__(self, ui, repo, inc, exc):
212 self.ui = ui
212 self.ui = ui
213 self.repo = repo
213 self.repo = repo
214 self.match = match.match(repo.root, '', [], inc, exc)
214 self.match = match.match(repo.root, '', [], inc, exc)
215 self.restrict = kwtools['hgcmd'] in restricted.split()
215 self.restrict = kwtools['hgcmd'] in restricted.split()
216 self.postcommit = False
216 self.postcommit = False
217
217
218 kwmaps = self.ui.configitems('keywordmaps')
218 kwmaps = self.ui.configitems('keywordmaps')
219 if kwmaps: # override default templates
219 if kwmaps: # override default templates
220 self.templates = dict(kwmaps)
220 self.templates = dict(kwmaps)
221 else:
221 else:
222 self.templates = _defaultkwmaps(self.ui)
222 self.templates = _defaultkwmaps(self.ui)
223
223
224 @util.propertycache
224 @util.propertycache
225 def escape(self):
225 def escape(self):
226 '''Returns bar-separated and escaped keywords.'''
226 '''Returns bar-separated and escaped keywords.'''
227 return '|'.join(map(re.escape, self.templates.keys()))
227 return '|'.join(map(re.escape, self.templates.keys()))
228
228
229 @util.propertycache
229 @util.propertycache
230 def rekw(self):
230 def rekw(self):
231 '''Returns regex for unexpanded keywords.'''
231 '''Returns regex for unexpanded keywords.'''
232 return re.compile(r'\$(%s)\$' % self.escape)
232 return re.compile(r'\$(%s)\$' % self.escape)
233
233
234 @util.propertycache
234 @util.propertycache
235 def rekwexp(self):
235 def rekwexp(self):
236 '''Returns regex for expanded keywords.'''
236 '''Returns regex for expanded keywords.'''
237 return re.compile(r'\$(%s): [^$\n\r]*? \$' % self.escape)
237 return re.compile(r'\$(%s): [^$\n\r]*? \$' % self.escape)
238
238
239 def substitute(self, data, path, ctx, subfunc):
239 def substitute(self, data, path, ctx, subfunc):
240 '''Replaces keywords in data with expanded template.'''
240 '''Replaces keywords in data with expanded template.'''
241 def kwsub(mobj):
241 def kwsub(mobj):
242 kw = mobj.group(1)
242 kw = mobj.group(1)
243 ct = cmdutil.changeset_templater(self.ui, self.repo, False, None,
243 ct = cmdutil.changeset_templater(self.ui, self.repo, False, None,
244 self.templates[kw], '', False)
244 self.templates[kw], '', False)
245 self.ui.pushbuffer()
245 self.ui.pushbuffer()
246 ct.show(ctx, root=self.repo.root, file=path)
246 ct.show(ctx, root=self.repo.root, file=path)
247 ekw = templatefilters.firstline(self.ui.popbuffer())
247 ekw = templatefilters.firstline(self.ui.popbuffer())
248 return '$%s: %s $' % (kw, ekw)
248 return '$%s: %s $' % (kw, ekw)
249 return subfunc(kwsub, data)
249 return subfunc(kwsub, data)
250
250
251 def linkctx(self, path, fileid):
251 def linkctx(self, path, fileid):
252 '''Similar to filelog.linkrev, but returns a changectx.'''
252 '''Similar to filelog.linkrev, but returns a changectx.'''
253 return self.repo.filectx(path, fileid=fileid).changectx()
253 return self.repo.filectx(path, fileid=fileid).changectx()
254
254
255 def expand(self, path, node, data):
255 def expand(self, path, node, data):
256 '''Returns data with keywords expanded.'''
256 '''Returns data with keywords expanded.'''
257 if not self.restrict and self.match(path) and not util.binary(data):
257 if not self.restrict and self.match(path) and not util.binary(data):
258 ctx = self.linkctx(path, node)
258 ctx = self.linkctx(path, node)
259 return self.substitute(data, path, ctx, self.rekw.sub)
259 return self.substitute(data, path, ctx, self.rekw.sub)
260 return data
260 return data
261
261
262 def iskwfile(self, cand, ctx):
262 def iskwfile(self, cand, ctx):
263 '''Returns subset of candidates which are configured for keyword
263 '''Returns subset of candidates which are configured for keyword
264 expansion but are not symbolic links.'''
264 expansion but are not symbolic links.'''
265 return [f for f in cand if self.match(f) and 'l' not in ctx.flags(f)]
265 return [f for f in cand if self.match(f) and 'l' not in ctx.flags(f)]
266
266
267 def overwrite(self, ctx, candidates, lookup, expand, rekw=False):
267 def overwrite(self, ctx, candidates, lookup, expand, rekw=False):
268 '''Overwrites selected files expanding/shrinking keywords.'''
268 '''Overwrites selected files expanding/shrinking keywords.'''
269 if self.restrict or lookup or self.postcommit: # exclude kw_copy
269 if self.restrict or lookup or self.postcommit: # exclude kw_copy
270 candidates = self.iskwfile(candidates, ctx)
270 candidates = self.iskwfile(candidates, ctx)
271 if not candidates:
271 if not candidates:
272 return
272 return
273 kwcmd = self.restrict and lookup # kwexpand/kwshrink
273 kwcmd = self.restrict and lookup # kwexpand/kwshrink
274 if self.restrict or expand and lookup:
274 if self.restrict or expand and lookup:
275 mf = ctx.manifest()
275 mf = ctx.manifest()
276 if self.restrict or rekw:
276 if self.restrict or rekw:
277 re_kw = self.rekw
277 re_kw = self.rekw
278 else:
278 else:
279 re_kw = self.rekwexp
279 re_kw = self.rekwexp
280 if expand:
280 if expand:
281 msg = _('overwriting %s expanding keywords\n')
281 msg = _('overwriting %s expanding keywords\n')
282 else:
282 else:
283 msg = _('overwriting %s shrinking keywords\n')
283 msg = _('overwriting %s shrinking keywords\n')
284 for f in candidates:
284 for f in candidates:
285 if self.restrict:
285 if self.restrict:
286 data = self.repo.file(f).read(mf[f])
286 data = self.repo.file(f).read(mf[f])
287 else:
287 else:
288 data = self.repo.wread(f)
288 data = self.repo.wread(f)
289 if util.binary(data):
289 if util.binary(data):
290 continue
290 continue
291 if expand:
291 if expand:
292 parents = ctx.parents()
292 parents = ctx.parents()
293 if lookup:
293 if lookup:
294 ctx = self.linkctx(f, mf[f])
294 ctx = self.linkctx(f, mf[f])
295 elif self.restrict and len(parents) > 1:
295 elif self.restrict and len(parents) > 1:
296 # merge commit
296 # merge commit
297 # in case of conflict f is in modified state during
297 # in case of conflict f is in modified state during
298 # merge, even if f does not differ from f in parent
298 # merge, even if f does not differ from f in parent
299 for p in parents:
299 for p in parents:
300 if f in p and not p[f].cmp(ctx[f]):
300 if f in p and not p[f].cmp(ctx[f]):
301 ctx = p[f].changectx()
301 ctx = p[f].changectx()
302 break
302 break
303 data, found = self.substitute(data, f, ctx, re_kw.subn)
303 data, found = self.substitute(data, f, ctx, re_kw.subn)
304 elif self.restrict:
304 elif self.restrict:
305 found = re_kw.search(data)
305 found = re_kw.search(data)
306 else:
306 else:
307 data, found = _shrinktext(data, re_kw.subn)
307 data, found = _shrinktext(data, re_kw.subn)
308 if found:
308 if found:
309 self.ui.note(msg % f)
309 self.ui.note(msg % f)
310 fp = self.repo.wvfs(f, "wb", atomictemp=True)
310 fp = self.repo.wvfs(f, "wb", atomictemp=True)
311 fp.write(data)
311 fp.write(data)
312 fp.close()
312 fp.close()
313 if kwcmd:
313 if kwcmd:
314 self.repo.dirstate.normal(f)
314 self.repo.dirstate.normal(f)
315 elif self.postcommit:
315 elif self.postcommit:
316 self.repo.dirstate.normallookup(f)
316 self.repo.dirstate.normallookup(f)
317
317
318 def shrink(self, fname, text):
318 def shrink(self, fname, text):
319 '''Returns text with all keyword substitutions removed.'''
319 '''Returns text with all keyword substitutions removed.'''
320 if self.match(fname) and not util.binary(text):
320 if self.match(fname) and not util.binary(text):
321 return _shrinktext(text, self.rekwexp.sub)
321 return _shrinktext(text, self.rekwexp.sub)
322 return text
322 return text
323
323
324 def shrinklines(self, fname, lines):
324 def shrinklines(self, fname, lines):
325 '''Returns lines with keyword substitutions removed.'''
325 '''Returns lines with keyword substitutions removed.'''
326 if self.match(fname):
326 if self.match(fname):
327 text = ''.join(lines)
327 text = ''.join(lines)
328 if not util.binary(text):
328 if not util.binary(text):
329 return _shrinktext(text, self.rekwexp.sub).splitlines(True)
329 return _shrinktext(text, self.rekwexp.sub).splitlines(True)
330 return lines
330 return lines
331
331
332 def wread(self, fname, data):
332 def wread(self, fname, data):
333 '''If in restricted mode returns data read from wdir with
333 '''If in restricted mode returns data read from wdir with
334 keyword substitutions removed.'''
334 keyword substitutions removed.'''
335 if self.restrict:
335 if self.restrict:
336 return self.shrink(fname, data)
336 return self.shrink(fname, data)
337 return data
337 return data
338
338
339 class kwfilelog(filelog.filelog):
339 class kwfilelog(filelog.filelog):
340 '''
340 '''
341 Subclass of filelog to hook into its read, add, cmp methods.
341 Subclass of filelog to hook into its read, add, cmp methods.
342 Keywords are "stored" unexpanded, and processed on reading.
342 Keywords are "stored" unexpanded, and processed on reading.
343 '''
343 '''
344 def __init__(self, opener, kwt, path):
344 def __init__(self, opener, kwt, path):
345 super(kwfilelog, self).__init__(opener, path)
345 super(kwfilelog, self).__init__(opener, path)
346 self.kwt = kwt
346 self.kwt = kwt
347 self.path = path
347 self.path = path
348
348
349 def read(self, node):
349 def read(self, node):
350 '''Expands keywords when reading filelog.'''
350 '''Expands keywords when reading filelog.'''
351 data = super(kwfilelog, self).read(node)
351 data = super(kwfilelog, self).read(node)
352 if self.renamed(node):
352 if self.renamed(node):
353 return data
353 return data
354 return self.kwt.expand(self.path, node, data)
354 return self.kwt.expand(self.path, node, data)
355
355
356 def add(self, text, meta, tr, link, p1=None, p2=None):
356 def add(self, text, meta, tr, link, p1=None, p2=None):
357 '''Removes keyword substitutions when adding to filelog.'''
357 '''Removes keyword substitutions when adding to filelog.'''
358 text = self.kwt.shrink(self.path, text)
358 text = self.kwt.shrink(self.path, text)
359 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
359 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
360
360
361 def cmp(self, node, text):
361 def cmp(self, node, text):
362 '''Removes keyword substitutions for comparison.'''
362 '''Removes keyword substitutions for comparison.'''
363 text = self.kwt.shrink(self.path, text)
363 text = self.kwt.shrink(self.path, text)
364 return super(kwfilelog, self).cmp(node, text)
364 return super(kwfilelog, self).cmp(node, text)
365
365
366 def _status(ui, repo, wctx, kwt, *pats, **opts):
366 def _status(ui, repo, wctx, kwt, *pats, **opts):
367 '''Bails out if [keyword] configuration is not active.
367 '''Bails out if [keyword] configuration is not active.
368 Returns status of working directory.'''
368 Returns status of working directory.'''
369 if kwt:
369 if kwt:
370 return repo.status(match=scmutil.match(wctx, pats, opts), clean=True,
370 return repo.status(match=scmutil.match(wctx, pats, opts), clean=True,
371 unknown=opts.get('unknown') or opts.get('all'))
371 unknown=opts.get('unknown') or opts.get('all'))
372 if ui.configitems('keyword'):
372 if ui.configitems('keyword'):
373 raise error.Abort(_('[keyword] patterns cannot match'))
373 raise error.Abort(_('[keyword] patterns cannot match'))
374 raise error.Abort(_('no [keyword] patterns configured'))
374 raise error.Abort(_('no [keyword] patterns configured'))
375
375
376 def _kwfwrite(ui, repo, expand, *pats, **opts):
376 def _kwfwrite(ui, repo, expand, *pats, **opts):
377 '''Selects files and passes them to kwtemplater.overwrite.'''
377 '''Selects files and passes them to kwtemplater.overwrite.'''
378 wctx = repo[None]
378 wctx = repo[None]
379 if len(wctx.parents()) > 1:
379 if len(wctx.parents()) > 1:
380 raise error.Abort(_('outstanding uncommitted merge'))
380 raise error.Abort(_('outstanding uncommitted merge'))
381 kwt = kwtools['templater']
381 kwt = kwtools['templater']
382 with repo.wlock():
382 with repo.wlock():
383 status = _status(ui, repo, wctx, kwt, *pats, **opts)
383 status = _status(ui, repo, wctx, kwt, *pats, **opts)
384 if status.modified or status.added or status.removed or status.deleted:
384 if status.modified or status.added or status.removed or status.deleted:
385 raise error.Abort(_('outstanding uncommitted changes'))
385 raise error.Abort(_('outstanding uncommitted changes'))
386 kwt.overwrite(wctx, status.clean, True, expand)
386 kwt.overwrite(wctx, status.clean, True, expand)
387
387
388 @command('kwdemo',
388 @command('kwdemo',
389 [('d', 'default', None, _('show default keyword template maps')),
389 [('d', 'default', None, _('show default keyword template maps')),
390 ('f', 'rcfile', '',
390 ('f', 'rcfile', '',
391 _('read maps from rcfile'), _('FILE'))],
391 _('read maps from rcfile'), _('FILE'))],
392 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...'),
392 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...'),
393 optionalrepo=True)
393 optionalrepo=True)
394 def demo(ui, repo, *args, **opts):
394 def demo(ui, repo, *args, **opts):
395 '''print [keywordmaps] configuration and an expansion example
395 '''print [keywordmaps] configuration and an expansion example
396
396
397 Show current, custom, or default keyword template maps and their
397 Show current, custom, or default keyword template maps and their
398 expansions.
398 expansions.
399
399
400 Extend the current configuration by specifying maps as arguments
400 Extend the current configuration by specifying maps as arguments
401 and using -f/--rcfile to source an external hgrc file.
401 and using -f/--rcfile to source an external hgrc file.
402
402
403 Use -d/--default to disable current configuration.
403 Use -d/--default to disable current configuration.
404
404
405 See :hg:`help templates` for information on templates and filters.
405 See :hg:`help templates` for information on templates and filters.
406 '''
406 '''
407 def demoitems(section, items):
407 def demoitems(section, items):
408 ui.write('[%s]\n' % section)
408 ui.write('[%s]\n' % section)
409 for k, v in sorted(items):
409 for k, v in sorted(items):
410 ui.write('%s = %s\n' % (k, v))
410 ui.write('%s = %s\n' % (k, v))
411
411
412 fn = 'demo.txt'
412 fn = 'demo.txt'
413 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
413 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
414 ui.note(_('creating temporary repository at %s\n') % tmpdir)
414 ui.note(_('creating temporary repository at %s\n') % tmpdir)
415 repo = localrepo.localrepository(repo.baseui, tmpdir, True)
415 repo = localrepo.localrepository(repo.baseui, tmpdir, True)
416 ui.setconfig('keyword', fn, '', 'keyword')
416 ui.setconfig('keyword', fn, '', 'keyword')
417 svn = ui.configbool('keywordset', 'svn')
417 svn = ui.configbool('keywordset', 'svn')
418 # explicitly set keywordset for demo output
418 # explicitly set keywordset for demo output
419 ui.setconfig('keywordset', 'svn', svn, 'keyword')
419 ui.setconfig('keywordset', 'svn', svn, 'keyword')
420
420
421 uikwmaps = ui.configitems('keywordmaps')
421 uikwmaps = ui.configitems('keywordmaps')
422 if args or opts.get('rcfile'):
422 if args or opts.get('rcfile'):
423 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
423 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
424 if uikwmaps:
424 if uikwmaps:
425 ui.status(_('\textending current template maps\n'))
425 ui.status(_('\textending current template maps\n'))
426 if opts.get('default') or not uikwmaps:
426 if opts.get('default') or not uikwmaps:
427 if svn:
427 if svn:
428 ui.status(_('\toverriding default svn keywordset\n'))
428 ui.status(_('\toverriding default svn keywordset\n'))
429 else:
429 else:
430 ui.status(_('\toverriding default cvs keywordset\n'))
430 ui.status(_('\toverriding default cvs keywordset\n'))
431 if opts.get('rcfile'):
431 if opts.get('rcfile'):
432 ui.readconfig(opts.get('rcfile'))
432 ui.readconfig(opts.get('rcfile'))
433 if args:
433 if args:
434 # simulate hgrc parsing
434 # simulate hgrc parsing
435 rcmaps = '[keywordmaps]\n%s\n' % '\n'.join(args)
435 rcmaps = '[keywordmaps]\n%s\n' % '\n'.join(args)
436 repo.vfs.write('hgrc', rcmaps)
436 repo.vfs.write('hgrc', rcmaps)
437 ui.readconfig(repo.join('hgrc'))
437 ui.readconfig(repo.join('hgrc'))
438 kwmaps = dict(ui.configitems('keywordmaps'))
438 kwmaps = dict(ui.configitems('keywordmaps'))
439 elif opts.get('default'):
439 elif opts.get('default'):
440 if svn:
440 if svn:
441 ui.status(_('\n\tconfiguration using default svn keywordset\n'))
441 ui.status(_('\n\tconfiguration using default svn keywordset\n'))
442 else:
442 else:
443 ui.status(_('\n\tconfiguration using default cvs keywordset\n'))
443 ui.status(_('\n\tconfiguration using default cvs keywordset\n'))
444 kwmaps = _defaultkwmaps(ui)
444 kwmaps = _defaultkwmaps(ui)
445 if uikwmaps:
445 if uikwmaps:
446 ui.status(_('\tdisabling current template maps\n'))
446 ui.status(_('\tdisabling current template maps\n'))
447 for k, v in kwmaps.iteritems():
447 for k, v in kwmaps.iteritems():
448 ui.setconfig('keywordmaps', k, v, 'keyword')
448 ui.setconfig('keywordmaps', k, v, 'keyword')
449 else:
449 else:
450 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
450 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
451 if uikwmaps:
451 if uikwmaps:
452 kwmaps = dict(uikwmaps)
452 kwmaps = dict(uikwmaps)
453 else:
453 else:
454 kwmaps = _defaultkwmaps(ui)
454 kwmaps = _defaultkwmaps(ui)
455
455
456 uisetup(ui)
456 uisetup(ui)
457 reposetup(ui, repo)
457 reposetup(ui, repo)
458 ui.write('[extensions]\nkeyword =\n')
458 ui.write('[extensions]\nkeyword =\n')
459 demoitems('keyword', ui.configitems('keyword'))
459 demoitems('keyword', ui.configitems('keyword'))
460 demoitems('keywordset', ui.configitems('keywordset'))
460 demoitems('keywordset', ui.configitems('keywordset'))
461 demoitems('keywordmaps', kwmaps.iteritems())
461 demoitems('keywordmaps', kwmaps.iteritems())
462 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
462 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
463 repo.wvfs.write(fn, keywords)
463 repo.wvfs.write(fn, keywords)
464 repo[None].add([fn])
464 repo[None].add([fn])
465 ui.note(_('\nkeywords written to %s:\n') % fn)
465 ui.note(_('\nkeywords written to %s:\n') % fn)
466 ui.note(keywords)
466 ui.note(keywords)
467 with repo.wlock():
467 with repo.wlock():
468 repo.dirstate.setbranch('demobranch')
468 repo.dirstate.setbranch('demobranch')
469 for name, cmd in ui.configitems('hooks'):
469 for name, cmd in ui.configitems('hooks'):
470 if name.split('.', 1)[0].find('commit') > -1:
470 if name.split('.', 1)[0].find('commit') > -1:
471 repo.ui.setconfig('hooks', name, '', 'keyword')
471 repo.ui.setconfig('hooks', name, '', 'keyword')
472 msg = _('hg keyword configuration and expansion example')
472 msg = _('hg keyword configuration and expansion example')
473 ui.note(("hg ci -m '%s'\n" % msg))
473 ui.note(("hg ci -m '%s'\n" % msg))
474 repo.commit(text=msg)
474 repo.commit(text=msg)
475 ui.status(_('\n\tkeywords expanded\n'))
475 ui.status(_('\n\tkeywords expanded\n'))
476 ui.write(repo.wread(fn))
476 ui.write(repo.wread(fn))
477 repo.wvfs.rmtree(repo.root)
477 repo.wvfs.rmtree(repo.root)
478
478
479 @command('kwexpand',
479 @command('kwexpand',
480 commands.walkopts,
480 commands.walkopts,
481 _('hg kwexpand [OPTION]... [FILE]...'),
481 _('hg kwexpand [OPTION]... [FILE]...'),
482 inferrepo=True)
482 inferrepo=True)
483 def expand(ui, repo, *pats, **opts):
483 def expand(ui, repo, *pats, **opts):
484 '''expand keywords in the working directory
484 '''expand keywords in the working directory
485
485
486 Run after (re)enabling keyword expansion.
486 Run after (re)enabling keyword expansion.
487
487
488 kwexpand refuses to run if given files contain local changes.
488 kwexpand refuses to run if given files contain local changes.
489 '''
489 '''
490 # 3rd argument sets expansion to True
490 # 3rd argument sets expansion to True
491 _kwfwrite(ui, repo, True, *pats, **opts)
491 _kwfwrite(ui, repo, True, *pats, **opts)
492
492
493 @command('kwfiles',
493 @command('kwfiles',
494 [('A', 'all', None, _('show keyword status flags of all files')),
494 [('A', 'all', None, _('show keyword status flags of all files')),
495 ('i', 'ignore', None, _('show files excluded from expansion')),
495 ('i', 'ignore', None, _('show files excluded from expansion')),
496 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
496 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
497 ] + commands.walkopts,
497 ] + commands.walkopts,
498 _('hg kwfiles [OPTION]... [FILE]...'),
498 _('hg kwfiles [OPTION]... [FILE]...'),
499 inferrepo=True)
499 inferrepo=True)
500 def files(ui, repo, *pats, **opts):
500 def files(ui, repo, *pats, **opts):
501 '''show files configured for keyword expansion
501 '''show files configured for keyword expansion
502
502
503 List which files in the working directory are matched by the
503 List which files in the working directory are matched by the
504 [keyword] configuration patterns.
504 [keyword] configuration patterns.
505
505
506 Useful to prevent inadvertent keyword expansion and to speed up
506 Useful to prevent inadvertent keyword expansion and to speed up
507 execution by including only files that are actual candidates for
507 execution by including only files that are actual candidates for
508 expansion.
508 expansion.
509
509
510 See :hg:`help keyword` on how to construct patterns both for
510 See :hg:`help keyword` on how to construct patterns both for
511 inclusion and exclusion of files.
511 inclusion and exclusion of files.
512
512
513 With -A/--all and -v/--verbose the codes used to show the status
513 With -A/--all and -v/--verbose the codes used to show the status
514 of files are::
514 of files are::
515
515
516 K = keyword expansion candidate
516 K = keyword expansion candidate
517 k = keyword expansion candidate (not tracked)
517 k = keyword expansion candidate (not tracked)
518 I = ignored
518 I = ignored
519 i = ignored (not tracked)
519 i = ignored (not tracked)
520 '''
520 '''
521 kwt = kwtools['templater']
521 kwt = kwtools['templater']
522 wctx = repo[None]
522 wctx = repo[None]
523 status = _status(ui, repo, wctx, kwt, *pats, **opts)
523 status = _status(ui, repo, wctx, kwt, *pats, **opts)
524 if pats:
524 if pats:
525 cwd = repo.getcwd()
525 cwd = repo.getcwd()
526 else:
526 else:
527 cwd = ''
527 cwd = ''
528 files = []
528 files = []
529 if not opts.get('unknown') or opts.get('all'):
529 if not opts.get('unknown') or opts.get('all'):
530 files = sorted(status.modified + status.added + status.clean)
530 files = sorted(status.modified + status.added + status.clean)
531 kwfiles = kwt.iskwfile(files, wctx)
531 kwfiles = kwt.iskwfile(files, wctx)
532 kwdeleted = kwt.iskwfile(status.deleted, wctx)
532 kwdeleted = kwt.iskwfile(status.deleted, wctx)
533 kwunknown = kwt.iskwfile(status.unknown, wctx)
533 kwunknown = kwt.iskwfile(status.unknown, wctx)
534 if not opts.get('ignore') or opts.get('all'):
534 if not opts.get('ignore') or opts.get('all'):
535 showfiles = kwfiles, kwdeleted, kwunknown
535 showfiles = kwfiles, kwdeleted, kwunknown
536 else:
536 else:
537 showfiles = [], [], []
537 showfiles = [], [], []
538 if opts.get('all') or opts.get('ignore'):
538 if opts.get('all') or opts.get('ignore'):
539 showfiles += ([f for f in files if f not in kwfiles],
539 showfiles += ([f for f in files if f not in kwfiles],
540 [f for f in status.unknown if f not in kwunknown])
540 [f for f in status.unknown if f not in kwunknown])
541 kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
541 kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
542 kwstates = zip(kwlabels, 'K!kIi', showfiles)
542 kwstates = zip(kwlabels, 'K!kIi', showfiles)
543 fm = ui.formatter('kwfiles', opts)
543 fm = ui.formatter('kwfiles', opts)
544 fmt = '%.0s%s\n'
544 fmt = '%.0s%s\n'
545 if opts.get('all') or ui.verbose:
545 if opts.get('all') or ui.verbose:
546 fmt = '%s %s\n'
546 fmt = '%s %s\n'
547 for kwstate, char, filenames in kwstates:
547 for kwstate, char, filenames in kwstates:
548 label = 'kwfiles.' + kwstate
548 label = 'kwfiles.' + kwstate
549 for f in filenames:
549 for f in filenames:
550 fm.startitem()
550 fm.startitem()
551 fm.write('kwstatus path', fmt, char,
551 fm.write('kwstatus path', fmt, char,
552 repo.pathto(f, cwd), label=label)
552 repo.pathto(f, cwd), label=label)
553 fm.end()
553 fm.end()
554
554
555 @command('kwshrink',
555 @command('kwshrink',
556 commands.walkopts,
556 commands.walkopts,
557 _('hg kwshrink [OPTION]... [FILE]...'),
557 _('hg kwshrink [OPTION]... [FILE]...'),
558 inferrepo=True)
558 inferrepo=True)
559 def shrink(ui, repo, *pats, **opts):
559 def shrink(ui, repo, *pats, **opts):
560 '''revert expanded keywords in the working directory
560 '''revert expanded keywords in the working directory
561
561
562 Must be run before changing/disabling active keywords.
562 Must be run before changing/disabling active keywords.
563
563
564 kwshrink refuses to run if given files contain local changes.
564 kwshrink refuses to run if given files contain local changes.
565 '''
565 '''
566 # 3rd argument sets expansion to False
566 # 3rd argument sets expansion to False
567 _kwfwrite(ui, repo, False, *pats, **opts)
567 _kwfwrite(ui, repo, False, *pats, **opts)
568
568
569
569
570 def uisetup(ui):
570 def uisetup(ui):
571 ''' Monkeypatches dispatch._parse to retrieve user command.'''
571 ''' Monkeypatches dispatch._parse to retrieve user command.'''
572
572
573 def kwdispatch_parse(orig, ui, args):
573 def kwdispatch_parse(orig, ui, args):
574 '''Monkeypatch dispatch._parse to obtain running hg command.'''
574 '''Monkeypatch dispatch._parse to obtain running hg command.'''
575 cmd, func, args, options, cmdoptions = orig(ui, args)
575 cmd, func, args, options, cmdoptions = orig(ui, args)
576 kwtools['hgcmd'] = cmd
576 kwtools['hgcmd'] = cmd
577 return cmd, func, args, options, cmdoptions
577 return cmd, func, args, options, cmdoptions
578
578
579 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
579 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
580
580
581 def reposetup(ui, repo):
581 def reposetup(ui, repo):
582 '''Sets up repo as kwrepo for keyword substitution.
582 '''Sets up repo as kwrepo for keyword substitution.
583 Overrides file method to return kwfilelog instead of filelog
583 Overrides file method to return kwfilelog instead of filelog
584 if file matches user configuration.
584 if file matches user configuration.
585 Wraps commit to overwrite configured files with updated
585 Wraps commit to overwrite configured files with updated
586 keyword substitutions.
586 keyword substitutions.
587 Monkeypatches patch and webcommands.'''
587 Monkeypatches patch and webcommands.'''
588
588
589 try:
589 try:
590 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
590 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
591 or '.hg' in util.splitpath(repo.root)
591 or '.hg' in util.splitpath(repo.root)
592 or repo._url.startswith('bundle:')):
592 or repo._url.startswith('bundle:')):
593 return
593 return
594 except AttributeError:
594 except AttributeError:
595 pass
595 pass
596
596
597 inc, exc = [], ['.hg*']
597 inc, exc = [], ['.hg*']
598 for pat, opt in ui.configitems('keyword'):
598 for pat, opt in ui.configitems('keyword'):
599 if opt != 'ignore':
599 if opt != 'ignore':
600 inc.append(pat)
600 inc.append(pat)
601 else:
601 else:
602 exc.append(pat)
602 exc.append(pat)
603 if not inc:
603 if not inc:
604 return
604 return
605
605
606 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
606 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
607
607
608 class kwrepo(repo.__class__):
608 class kwrepo(repo.__class__):
609 def file(self, f):
609 def file(self, f):
610 if f[0] == '/':
610 if f[0] == '/':
611 f = f[1:]
611 f = f[1:]
612 return kwfilelog(self.svfs, kwt, f)
612 return kwfilelog(self.svfs, kwt, f)
613
613
614 def wread(self, filename):
614 def wread(self, filename):
615 data = super(kwrepo, self).wread(filename)
615 data = super(kwrepo, self).wread(filename)
616 return kwt.wread(filename, data)
616 return kwt.wread(filename, data)
617
617
618 def commit(self, *args, **opts):
618 def commit(self, *args, **opts):
619 # use custom commitctx for user commands
619 # use custom commitctx for user commands
620 # other extensions can still wrap repo.commitctx directly
620 # other extensions can still wrap repo.commitctx directly
621 self.commitctx = self.kwcommitctx
621 self.commitctx = self.kwcommitctx
622 try:
622 try:
623 return super(kwrepo, self).commit(*args, **opts)
623 return super(kwrepo, self).commit(*args, **opts)
624 finally:
624 finally:
625 del self.commitctx
625 del self.commitctx
626
626
627 def kwcommitctx(self, ctx, error=False):
627 def kwcommitctx(self, ctx, error=False):
628 n = super(kwrepo, self).commitctx(ctx, error)
628 n = super(kwrepo, self).commitctx(ctx, error)
629 # no lock needed, only called from repo.commit() which already locks
629 # no lock needed, only called from repo.commit() which already locks
630 if not kwt.postcommit:
630 if not kwt.postcommit:
631 restrict = kwt.restrict
631 restrict = kwt.restrict
632 kwt.restrict = True
632 kwt.restrict = True
633 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
633 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
634 False, True)
634 False, True)
635 kwt.restrict = restrict
635 kwt.restrict = restrict
636 return n
636 return n
637
637
638 def rollback(self, dryrun=False, force=False):
638 def rollback(self, dryrun=False, force=False):
639 wlock = self.wlock()
639 wlock = self.wlock()
640 origrestrict = kwt.restrict
640 origrestrict = kwt.restrict
641 try:
641 try:
642 if not dryrun:
642 if not dryrun:
643 changed = self['.'].files()
643 changed = self['.'].files()
644 ret = super(kwrepo, self).rollback(dryrun, force)
644 ret = super(kwrepo, self).rollback(dryrun, force)
645 if not dryrun:
645 if not dryrun:
646 ctx = self['.']
646 ctx = self['.']
647 modified, added = _preselect(ctx.status(), changed)
647 modified, added = _preselect(ctx.status(), changed)
648 kwt.restrict = False
648 kwt.restrict = False
649 kwt.overwrite(ctx, modified, True, True)
649 kwt.overwrite(ctx, modified, True, True)
650 kwt.overwrite(ctx, added, True, False)
650 kwt.overwrite(ctx, added, True, False)
651 return ret
651 return ret
652 finally:
652 finally:
653 kwt.restrict = origrestrict
653 kwt.restrict = origrestrict
654 wlock.release()
654 wlock.release()
655
655
656 # monkeypatches
656 # monkeypatches
657 def kwpatchfile_init(orig, self, ui, gp, backend, store, eolmode=None):
657 def kwpatchfile_init(orig, self, ui, gp, backend, store, eolmode=None):
658 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
658 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
659 rejects or conflicts due to expanded keywords in working dir.'''
659 rejects or conflicts due to expanded keywords in working dir.'''
660 orig(self, ui, gp, backend, store, eolmode)
660 orig(self, ui, gp, backend, store, eolmode)
661 # shrink keywords read from working dir
661 # shrink keywords read from working dir
662 self.lines = kwt.shrinklines(self.fname, self.lines)
662 self.lines = kwt.shrinklines(self.fname, self.lines)
663
663
664 def kwdiff(orig, *args, **kwargs):
664 def kwdiff(orig, *args, **kwargs):
665 '''Monkeypatch patch.diff to avoid expansion.'''
665 '''Monkeypatch patch.diff to avoid expansion.'''
666 kwt.restrict = True
666 kwt.restrict = True
667 return orig(*args, **kwargs)
667 return orig(*args, **kwargs)
668
668
669 def kwweb_skip(orig, web, req, tmpl):
669 def kwweb_skip(orig, web, req, tmpl):
670 '''Wraps webcommands.x turning off keyword expansion.'''
670 '''Wraps webcommands.x turning off keyword expansion.'''
671 kwt.match = util.never
671 kwt.match = util.never
672 return orig(web, req, tmpl)
672 return orig(web, req, tmpl)
673
673
674 def kw_amend(orig, ui, repo, commitfunc, old, extra, pats, opts):
674 def kw_amend(orig, ui, repo, commitfunc, old, extra, pats, opts):
675 '''Wraps cmdutil.amend expanding keywords after amend.'''
675 '''Wraps cmdutil.amend expanding keywords after amend.'''
676 with repo.wlock():
676 with repo.wlock():
677 kwt.postcommit = True
677 kwt.postcommit = True
678 newid = orig(ui, repo, commitfunc, old, extra, pats, opts)
678 newid = orig(ui, repo, commitfunc, old, extra, pats, opts)
679 if newid != old.node():
679 if newid != old.node():
680 ctx = repo[newid]
680 ctx = repo[newid]
681 kwt.restrict = True
681 kwt.restrict = True
682 kwt.overwrite(ctx, ctx.files(), False, True)
682 kwt.overwrite(ctx, ctx.files(), False, True)
683 kwt.restrict = False
683 kwt.restrict = False
684 return newid
684 return newid
685
685
686 def kw_copy(orig, ui, repo, pats, opts, rename=False):
686 def kw_copy(orig, ui, repo, pats, opts, rename=False):
687 '''Wraps cmdutil.copy so that copy/rename destinations do not
687 '''Wraps cmdutil.copy so that copy/rename destinations do not
688 contain expanded keywords.
688 contain expanded keywords.
689 Note that the source of a regular file destination may also be a
689 Note that the source of a regular file destination may also be a
690 symlink:
690 symlink:
691 hg cp sym x -> x is symlink
691 hg cp sym x -> x is symlink
692 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords)
692 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords)
693 For the latter we have to follow the symlink to find out whether its
693 For the latter we have to follow the symlink to find out whether its
694 target is configured for expansion and we therefore must unexpand the
694 target is configured for expansion and we therefore must unexpand the
695 keywords in the destination.'''
695 keywords in the destination.'''
696 with repo.wlock():
696 with repo.wlock():
697 orig(ui, repo, pats, opts, rename)
697 orig(ui, repo, pats, opts, rename)
698 if opts.get('dry_run'):
698 if opts.get('dry_run'):
699 return
699 return
700 wctx = repo[None]
700 wctx = repo[None]
701 cwd = repo.getcwd()
701 cwd = repo.getcwd()
702
702
703 def haskwsource(dest):
703 def haskwsource(dest):
704 '''Returns true if dest is a regular file and configured for
704 '''Returns true if dest is a regular file and configured for
705 expansion or a symlink which points to a file configured for
705 expansion or a symlink which points to a file configured for
706 expansion. '''
706 expansion. '''
707 source = repo.dirstate.copied(dest)
707 source = repo.dirstate.copied(dest)
708 if 'l' in wctx.flags(source):
708 if 'l' in wctx.flags(source):
709 source = pathutil.canonpath(repo.root, cwd,
709 source = pathutil.canonpath(repo.root, cwd,
710 os.path.realpath(source))
710 os.path.realpath(source))
711 return kwt.match(source)
711 return kwt.match(source)
712
712
713 candidates = [f for f in repo.dirstate.copies() if
713 candidates = [f for f in repo.dirstate.copies() if
714 'l' not in wctx.flags(f) and haskwsource(f)]
714 'l' not in wctx.flags(f) and haskwsource(f)]
715 kwt.overwrite(wctx, candidates, False, False)
715 kwt.overwrite(wctx, candidates, False, False)
716
716
717 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
717 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
718 '''Wraps record.dorecord expanding keywords after recording.'''
718 '''Wraps record.dorecord expanding keywords after recording.'''
719 with repo.wlock():
719 with repo.wlock():
720 # record returns 0 even when nothing has changed
720 # record returns 0 even when nothing has changed
721 # therefore compare nodes before and after
721 # therefore compare nodes before and after
722 kwt.postcommit = True
722 kwt.postcommit = True
723 ctx = repo['.']
723 ctx = repo['.']
724 wstatus = ctx.status()
724 wstatus = ctx.status()
725 ret = orig(ui, repo, commitfunc, *pats, **opts)
725 ret = orig(ui, repo, commitfunc, *pats, **opts)
726 recctx = repo['.']
726 recctx = repo['.']
727 if ctx != recctx:
727 if ctx != recctx:
728 modified, added = _preselect(wstatus, recctx.files())
728 modified, added = _preselect(wstatus, recctx.files())
729 kwt.restrict = False
729 kwt.restrict = False
730 kwt.overwrite(recctx, modified, False, True)
730 kwt.overwrite(recctx, modified, False, True)
731 kwt.overwrite(recctx, added, False, True, True)
731 kwt.overwrite(recctx, added, False, True, True)
732 kwt.restrict = True
732 kwt.restrict = True
733 return ret
733 return ret
734
734
735 def kwfilectx_cmp(orig, self, fctx):
735 def kwfilectx_cmp(orig, self, fctx):
736 # keyword affects data size, comparing wdir and filelog size does
736 # keyword affects data size, comparing wdir and filelog size does
737 # not make sense
737 # not make sense
738 if (fctx._filerev is None and
738 if (fctx._filenode is None and
739 (self._repo._encodefilterpats or
739 (self._repo._encodefilterpats or
740 kwt.match(fctx.path()) and 'l' not in fctx.flags() or
740 kwt.match(fctx.path()) and 'l' not in fctx.flags() or
741 self.size() - 4 == fctx.size()) or
741 self.size() - 4 == fctx.size()) or
742 self.size() == fctx.size()):
742 self.size() == fctx.size()):
743 return self._filelog.cmp(self._filenode, fctx.data())
743 return self._filelog.cmp(self._filenode, fctx.data())
744 return True
744 return True
745
745
746 extensions.wrapfunction(context.filectx, 'cmp', kwfilectx_cmp)
746 extensions.wrapfunction(context.filectx, 'cmp', kwfilectx_cmp)
747 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
747 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
748 extensions.wrapfunction(patch, 'diff', kwdiff)
748 extensions.wrapfunction(patch, 'diff', kwdiff)
749 extensions.wrapfunction(cmdutil, 'amend', kw_amend)
749 extensions.wrapfunction(cmdutil, 'amend', kw_amend)
750 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
750 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
751 extensions.wrapfunction(cmdutil, 'dorecord', kw_dorecord)
751 extensions.wrapfunction(cmdutil, 'dorecord', kw_dorecord)
752 for c in 'annotate changeset rev filediff diff'.split():
752 for c in 'annotate changeset rev filediff diff'.split():
753 extensions.wrapfunction(webcommands, c, kwweb_skip)
753 extensions.wrapfunction(webcommands, c, kwweb_skip)
754 repo.__class__ = kwrepo
754 repo.__class__ = kwrepo
General Comments 0
You need to be logged in to leave comments. Login now