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