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