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