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