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