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