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