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