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