##// END OF EJS Templates
keyword: fix symlink detection under Windows
Patrick Mezard -
r6060:e4d74100 default
parent child Browse files
Show More
@@ -1,531 +1,533 b''
1 # keyword.py - $Keyword$ expansion for Mercurial
1 # keyword.py - $Keyword$ expansion for Mercurial
2 #
2 #
3 # Copyright 2007, 2008 Christian Ebert <blacktrash@gmx.net>
3 # Copyright 2007, 2008 Christian Ebert <blacktrash@gmx.net>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7 #
7 #
8 # $Id$
8 # $Id$
9 #
9 #
10 # Keyword expansion hack against the grain of a DSCM
10 # Keyword expansion hack against the grain of a DSCM
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 audience
14 # files (like LaTeX packages), that are mostly addressed to an audience
15 # not running a version control system.
15 # not running a version control system.
16 #
16 #
17 # For in-depth discussion refer to
17 # For in-depth discussion refer to
18 # <http://www.selenic.com/mercurial/wiki/index.cgi/KeywordPlan>.
18 # <http://www.selenic.com/mercurial/wiki/index.cgi/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 # Setup in hgrc:
24 # Setup in hgrc:
25 #
25 #
26 # [extensions]
26 # [extensions]
27 # # enable extension
27 # # enable extension
28 # hgext.keyword =
28 # hgext.keyword =
29 #
29 #
30 # Files to act upon/ignore are specified in the [keyword] section.
30 # Files to act upon/ignore are specified in the [keyword] section.
31 # Customized keyword template mappings in the [keywordmaps] section.
31 # Customized keyword template mappings in the [keywordmaps] section.
32 #
32 #
33 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
33 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
34
34
35 '''keyword expansion in local repositories
35 '''keyword expansion in local repositories
36
36
37 This extension expands RCS/CVS-like or self-customized $Keywords$
37 This extension expands RCS/CVS-like or self-customized $Keywords$
38 in tracked text files selected by your configuration.
38 in tracked text files selected by your configuration.
39
39
40 Keywords are only expanded in local repositories and not stored in
40 Keywords are only expanded in local repositories and not stored in
41 the change history. The mechanism can be regarded as a convenience
41 the change history. The mechanism can be regarded as a convenience
42 for the current user or for archive distribution.
42 for the current user or for archive distribution.
43
43
44 Configuration is done in the [keyword] and [keywordmaps] sections
44 Configuration is done in the [keyword] and [keywordmaps] sections
45 of hgrc files.
45 of hgrc files.
46
46
47 Example:
47 Example:
48
48
49 [keyword]
49 [keyword]
50 # expand keywords in every python file except those matching "x*"
50 # expand keywords in every python file except those matching "x*"
51 **.py =
51 **.py =
52 x* = ignore
52 x* = ignore
53
53
54 Note: the more specific you are in your filename patterns
54 Note: the more specific you are in your filename patterns
55 the less you lose speed in huge repos.
55 the less you lose speed in huge repos.
56
56
57 For [keywordmaps] template mapping and expansion demonstration and
57 For [keywordmaps] template mapping and expansion demonstration and
58 control run "hg kwdemo".
58 control run "hg kwdemo".
59
59
60 An additional date template filter {date|utcdate} is provided.
60 An additional date template filter {date|utcdate} is provided.
61
61
62 The default template mappings (view with "hg kwdemo -d") can be replaced
62 The default template mappings (view with "hg kwdemo -d") can be replaced
63 with customized keywords and templates.
63 with customized keywords and templates.
64 Again, run "hg kwdemo" to control the results of your config changes.
64 Again, run "hg kwdemo" to control the results of your config changes.
65
65
66 Before changing/disabling active keywords, run "hg kwshrink" to avoid
66 Before changing/disabling active keywords, run "hg kwshrink" to avoid
67 the risk of inadvertedly storing expanded keywords in the change history.
67 the risk of inadvertedly storing expanded keywords in the change history.
68
68
69 To force expansion after enabling it, or a configuration change, run
69 To force expansion after enabling it, or a configuration change, run
70 "hg kwexpand".
70 "hg kwexpand".
71
71
72 Also, when committing with the record extension or using mq's qrecord, be aware
72 Also, when committing with the record extension or using mq's qrecord, be aware
73 that keywords cannot be updated. Again, run "hg kwexpand" on the files in
73 that keywords cannot be updated. Again, run "hg kwexpand" on the files in
74 question to update keyword expansions after all changes have been checked in.
74 question to update keyword expansions after all changes have been checked in.
75
75
76 Expansions spanning more than one line and incremental expansions,
76 Expansions spanning more than one line and incremental expansions,
77 like CVS' $Log$, are not supported. A keyword template map
77 like CVS' $Log$, are not supported. A keyword template map
78 "Log = {desc}" expands to the first line of the changeset description.
78 "Log = {desc}" expands to the first line of the changeset description.
79 '''
79 '''
80
80
81 from mercurial import commands, cmdutil, context, dispatch, filelog, revlog
81 from mercurial import commands, cmdutil, context, dispatch, filelog, revlog
82 from mercurial import patch, localrepo, templater, templatefilters, util
82 from mercurial import patch, localrepo, templater, templatefilters, util
83 from mercurial.node import *
83 from mercurial.node import *
84 from mercurial.i18n import _
84 from mercurial.i18n import _
85 import re, shutil, tempfile, time, os
85 import re, shutil, tempfile, time, os
86
86
87 commands.optionalrepo += ' kwdemo'
87 commands.optionalrepo += ' kwdemo'
88
88
89 # hg commands that do not act on keywords
89 # hg commands that do not act on keywords
90 nokwcommands = ('add addremove bundle copy export grep identify incoming init'
90 nokwcommands = ('add addremove bundle copy export grep identify incoming init'
91 ' log outgoing push remove rename rollback tip convert email')
91 ' log outgoing push remove rename rollback tip convert email')
92
92
93 # hg commands that trigger expansion only when writing to working dir,
93 # hg commands that trigger expansion only when writing to working dir,
94 # not when reading filelog, and unexpand when reading from working dir
94 # not when reading filelog, and unexpand when reading from working dir
95 restricted = 'diff1 record qfold qimport qnew qpush qrefresh qrecord'
95 restricted = 'diff1 record qfold qimport qnew qpush qrefresh qrecord'
96
96
97 def utcdate(date):
97 def utcdate(date):
98 '''Returns hgdate in cvs-like UTC format.'''
98 '''Returns hgdate in cvs-like UTC format.'''
99 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
99 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
100
100
101
101
102 _kwtemplater, _cmd, _cmdoptions = None, None, None
102 _kwtemplater, _cmd, _cmdoptions = None, None, None
103
103
104 # store originals of monkeypatches
104 # store originals of monkeypatches
105 _patchfile_init = patch.patchfile.__init__
105 _patchfile_init = patch.patchfile.__init__
106 _dispatch_parse = dispatch._parse
106 _dispatch_parse = dispatch._parse
107
107
108 def _kwpatchfile_init(self, ui, fname, missing=False):
108 def _kwpatchfile_init(self, ui, fname, missing=False):
109 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
109 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
110 rejects or conflicts due to expanded keywords in working dir.'''
110 rejects or conflicts due to expanded keywords in working dir.'''
111 _patchfile_init(self, ui, fname, missing=missing)
111 _patchfile_init(self, ui, fname, missing=missing)
112 if _kwtemplater.matcher(self.fname):
112 if _kwtemplater.matcher(self.fname):
113 # shrink keywords read from working dir
113 # shrink keywords read from working dir
114 kwshrunk = _kwtemplater.shrink(''.join(self.lines))
114 kwshrunk = _kwtemplater.shrink(''.join(self.lines))
115 self.lines = kwshrunk.splitlines(True)
115 self.lines = kwshrunk.splitlines(True)
116
116
117 def _kwdispatch_parse(ui, args):
117 def _kwdispatch_parse(ui, args):
118 '''Monkeypatch dispatch._parse to obtain
118 '''Monkeypatch dispatch._parse to obtain
119 current command and command options (global _cmd, _cmdoptions).'''
119 current command and command options (global _cmd, _cmdoptions).'''
120 global _cmd, _cmdoptions
120 global _cmd, _cmdoptions
121 _cmd, func, args, options, _cmdoptions = _dispatch_parse(ui, args)
121 _cmd, func, args, options, _cmdoptions = _dispatch_parse(ui, args)
122 return _cmd, func, args, options, _cmdoptions
122 return _cmd, func, args, options, _cmdoptions
123
123
124 dispatch._parse = _kwdispatch_parse
124 dispatch._parse = _kwdispatch_parse
125
125
126
126
127 class kwtemplater(object):
127 class kwtemplater(object):
128 '''
128 '''
129 Sets up keyword templates, corresponding keyword regex, and
129 Sets up keyword templates, corresponding keyword regex, and
130 provides keyword substitution functions.
130 provides keyword substitution functions.
131 '''
131 '''
132 templates = {
132 templates = {
133 'Revision': '{node|short}',
133 'Revision': '{node|short}',
134 'Author': '{author|user}',
134 'Author': '{author|user}',
135 'Date': '{date|utcdate}',
135 'Date': '{date|utcdate}',
136 'RCSFile': '{file|basename},v',
136 'RCSFile': '{file|basename},v',
137 'Source': '{root}/{file},v',
137 'Source': '{root}/{file},v',
138 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
138 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
139 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
139 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
140 }
140 }
141
141
142 def __init__(self, ui, repo, inc, exc, restrict):
142 def __init__(self, ui, repo, inc, exc, restrict):
143 self.ui = ui
143 self.ui = ui
144 self.repo = repo
144 self.repo = repo
145 self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
145 self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
146 self.restrict = restrict
146 self.restrict = restrict
147 self.commitnode = None
147 self.commitnode = None
148 self.path = ''
148 self.path = ''
149
149
150 kwmaps = self.ui.configitems('keywordmaps')
150 kwmaps = self.ui.configitems('keywordmaps')
151 if kwmaps: # override default templates
151 if kwmaps: # override default templates
152 kwmaps = [(k, templater.parsestring(v, quoted=False))
152 kwmaps = [(k, templater.parsestring(v, quoted=False))
153 for (k, v) in kwmaps]
153 for (k, v) in kwmaps]
154 self.templates = dict(kwmaps)
154 self.templates = dict(kwmaps)
155 escaped = map(re.escape, self.templates.keys())
155 escaped = map(re.escape, self.templates.keys())
156 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
156 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
157 self.re_kw = re.compile(kwpat)
157 self.re_kw = re.compile(kwpat)
158
158
159 templatefilters.filters['utcdate'] = utcdate
159 templatefilters.filters['utcdate'] = utcdate
160 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
160 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
161 False, '', False)
161 False, '', False)
162
162
163 def substitute(self, node, data, subfunc):
163 def substitute(self, node, data, subfunc):
164 '''Obtains file's changenode if commit node not given,
164 '''Obtains file's changenode if commit node not given,
165 and calls given substitution function.'''
165 and calls given substitution function.'''
166 if self.commitnode:
166 if self.commitnode:
167 fnode = self.commitnode
167 fnode = self.commitnode
168 else:
168 else:
169 c = context.filectx(self.repo, self.path, fileid=node)
169 c = context.filectx(self.repo, self.path, fileid=node)
170 fnode = c.node()
170 fnode = c.node()
171
171
172 def kwsub(mobj):
172 def kwsub(mobj):
173 '''Substitutes keyword using corresponding template.'''
173 '''Substitutes keyword using corresponding template.'''
174 kw = mobj.group(1)
174 kw = mobj.group(1)
175 self.ct.use_template(self.templates[kw])
175 self.ct.use_template(self.templates[kw])
176 self.ui.pushbuffer()
176 self.ui.pushbuffer()
177 self.ct.show(changenode=fnode, root=self.repo.root, file=self.path)
177 self.ct.show(changenode=fnode, root=self.repo.root, file=self.path)
178 ekw = templatefilters.firstline(self.ui.popbuffer())
178 ekw = templatefilters.firstline(self.ui.popbuffer())
179 return '$%s: %s $' % (kw, ekw)
179 return '$%s: %s $' % (kw, ekw)
180
180
181 return subfunc(kwsub, data)
181 return subfunc(kwsub, data)
182
182
183 def expand(self, node, data):
183 def expand(self, node, data):
184 '''Returns data with keywords expanded.'''
184 '''Returns data with keywords expanded.'''
185 if self.restrict or util.binary(data):
185 if self.restrict or util.binary(data):
186 return data
186 return data
187 return self.substitute(node, data, self.re_kw.sub)
187 return self.substitute(node, data, self.re_kw.sub)
188
188
189 def process(self, node, data, expand):
189 def process(self, node, data, expand):
190 '''Returns a tuple: data, count.
190 '''Returns a tuple: data, count.
191 Count is number of keywords/keyword substitutions,
191 Count is number of keywords/keyword substitutions,
192 telling caller whether to act on file containing data.'''
192 telling caller whether to act on file containing data.'''
193 if util.binary(data):
193 if util.binary(data):
194 return data, None
194 return data, None
195 if expand:
195 if expand:
196 return self.substitute(node, data, self.re_kw.subn)
196 return self.substitute(node, data, self.re_kw.subn)
197 return data, self.re_kw.search(data)
197 return data, self.re_kw.search(data)
198
198
199 def shrink(self, text):
199 def shrink(self, text):
200 '''Returns text with all keyword substitutions removed.'''
200 '''Returns text with all keyword substitutions removed.'''
201 if util.binary(text):
201 if util.binary(text):
202 return text
202 return text
203 return self.re_kw.sub(r'$\1$', text)
203 return self.re_kw.sub(r'$\1$', text)
204
204
205 class kwfilelog(filelog.filelog):
205 class kwfilelog(filelog.filelog):
206 '''
206 '''
207 Subclass of filelog to hook into its read, add, cmp methods.
207 Subclass of filelog to hook into its read, add, cmp methods.
208 Keywords are "stored" unexpanded, and processed on reading.
208 Keywords are "stored" unexpanded, and processed on reading.
209 '''
209 '''
210 def __init__(self, opener, path):
210 def __init__(self, opener, path):
211 super(kwfilelog, self).__init__(opener, path)
211 super(kwfilelog, self).__init__(opener, path)
212 _kwtemplater.path = path
212 _kwtemplater.path = path
213
213
214 def kwctread(self, node, expand):
214 def kwctread(self, node, expand):
215 '''Reads expanding and counting keywords, called from _overwrite.'''
215 '''Reads expanding and counting keywords, called from _overwrite.'''
216 data = super(kwfilelog, self).read(node)
216 data = super(kwfilelog, self).read(node)
217 return _kwtemplater.process(node, data, expand)
217 return _kwtemplater.process(node, data, expand)
218
218
219 def read(self, node):
219 def read(self, node):
220 '''Expands keywords when reading filelog.'''
220 '''Expands keywords when reading filelog.'''
221 data = super(kwfilelog, self).read(node)
221 data = super(kwfilelog, self).read(node)
222 return _kwtemplater.expand(node, data)
222 return _kwtemplater.expand(node, data)
223
223
224 def add(self, text, meta, tr, link, p1=None, p2=None):
224 def add(self, text, meta, tr, link, p1=None, p2=None):
225 '''Removes keyword substitutions when adding to filelog.'''
225 '''Removes keyword substitutions when adding to filelog.'''
226 text = _kwtemplater.shrink(text)
226 text = _kwtemplater.shrink(text)
227 return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2)
227 return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2)
228
228
229 def cmp(self, node, text):
229 def cmp(self, node, text):
230 '''Removes keyword substitutions for comparison.'''
230 '''Removes keyword substitutions for comparison.'''
231 text = _kwtemplater.shrink(text)
231 text = _kwtemplater.shrink(text)
232 if self.renamed(node):
232 if self.renamed(node):
233 t2 = super(kwfilelog, self).read(node)
233 t2 = super(kwfilelog, self).read(node)
234 return t2 != text
234 return t2 != text
235 return revlog.revlog.cmp(self, node, text)
235 return revlog.revlog.cmp(self, node, text)
236
236
237 def _iskwfile(f, link):
237 def _iskwfile(f, link):
238 return not link(f) and _kwtemplater.matcher(f)
238 return not link(f) and _kwtemplater.matcher(f)
239
239
240 def _status(ui, repo, *pats, **opts):
240 def _status(ui, repo, *pats, **opts):
241 '''Bails out if [keyword] configuration is not active.
241 '''Bails out if [keyword] configuration is not active.
242 Returns status of working directory.'''
242 Returns status of working directory.'''
243 if _kwtemplater:
243 if _kwtemplater:
244 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
244 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
245 return repo.status(files=files, match=match, list_clean=True)
245 return repo.status(files=files, match=match, list_clean=True)
246 if ui.configitems('keyword'):
246 if ui.configitems('keyword'):
247 raise util.Abort(_('[keyword] patterns cannot match'))
247 raise util.Abort(_('[keyword] patterns cannot match'))
248 raise util.Abort(_('no [keyword] patterns configured'))
248 raise util.Abort(_('no [keyword] patterns configured'))
249
249
250 def _overwrite(ui, repo, node=None, expand=True, files=None):
250 def _overwrite(ui, repo, node=None, expand=True, files=None):
251 '''Overwrites selected files expanding/shrinking keywords.'''
251 '''Overwrites selected files expanding/shrinking keywords.'''
252 ctx = repo.changectx(node)
252 ctx = repo.changectx(node)
253 mf = ctx.manifest()
253 mf = ctx.manifest()
254 if node is not None: # commit
254 if node is not None: # commit
255 _kwtemplater.commitnode = node
255 _kwtemplater.commitnode = node
256 files = [f for f in ctx.files() if f in mf]
256 files = [f for f in ctx.files() if f in mf]
257 notify = ui.debug
257 notify = ui.debug
258 else: # kwexpand/kwshrink
258 else: # kwexpand/kwshrink
259 notify = ui.note
259 notify = ui.note
260 candidates = [f for f in files if _iskwfile(f, mf.linkf)]
260 candidates = [f for f in files if _iskwfile(f, mf.linkf)]
261 if candidates:
261 if candidates:
262 candidates.sort()
262 candidates.sort()
263 action = expand and 'expanding' or 'shrinking'
263 action = expand and 'expanding' or 'shrinking'
264 for f in candidates:
264 for f in candidates:
265 fp = repo.file(f, kwmatch=True)
265 fp = repo.file(f, kwmatch=True)
266 data, kwfound = fp.kwctread(mf[f], expand)
266 data, kwfound = fp.kwctread(mf[f], expand)
267 if kwfound:
267 if kwfound:
268 notify(_('overwriting %s %s keywords\n') % (f, action))
268 notify(_('overwriting %s %s keywords\n') % (f, action))
269 repo.wwrite(f, data, mf.flags(f))
269 repo.wwrite(f, data, mf.flags(f))
270 repo.dirstate.normal(f)
270 repo.dirstate.normal(f)
271
271
272 def _kwfwrite(ui, repo, expand, *pats, **opts):
272 def _kwfwrite(ui, repo, expand, *pats, **opts):
273 '''Selects files and passes them to _overwrite.'''
273 '''Selects files and passes them to _overwrite.'''
274 status = _status(ui, repo, *pats, **opts)
274 status = _status(ui, repo, *pats, **opts)
275 modified, added, removed, deleted, unknown, ignored, clean = status
275 modified, added, removed, deleted, unknown, ignored, clean = status
276 if modified or added or removed or deleted:
276 if modified or added or removed or deleted:
277 raise util.Abort(_('outstanding uncommitted changes in given files'))
277 raise util.Abort(_('outstanding uncommitted changes in given files'))
278 wlock = lock = None
278 wlock = lock = None
279 try:
279 try:
280 wlock = repo.wlock()
280 wlock = repo.wlock()
281 lock = repo.lock()
281 lock = repo.lock()
282 _overwrite(ui, repo, expand=expand, files=clean)
282 _overwrite(ui, repo, expand=expand, files=clean)
283 finally:
283 finally:
284 del wlock, lock
284 del wlock, lock
285
285
286
286
287 def demo(ui, repo, *args, **opts):
287 def demo(ui, repo, *args, **opts):
288 '''print [keywordmaps] configuration and an expansion example
288 '''print [keywordmaps] configuration and an expansion example
289
289
290 Show current, custom, or default keyword template maps
290 Show current, custom, or default keyword template maps
291 and their expansion.
291 and their expansion.
292
292
293 Extend current configuration by specifying maps as arguments
293 Extend current configuration by specifying maps as arguments
294 and optionally by reading from an additional hgrc file.
294 and optionally by reading from an additional hgrc file.
295
295
296 Override current keyword template maps with "default" option.
296 Override current keyword template maps with "default" option.
297 '''
297 '''
298 def demostatus(stat):
298 def demostatus(stat):
299 ui.status(_('\n\t%s\n') % stat)
299 ui.status(_('\n\t%s\n') % stat)
300
300
301 def demoitems(section, items):
301 def demoitems(section, items):
302 ui.write('[%s]\n' % section)
302 ui.write('[%s]\n' % section)
303 for k, v in items:
303 for k, v in items:
304 ui.write('%s = %s\n' % (k, v))
304 ui.write('%s = %s\n' % (k, v))
305
305
306 msg = 'hg keyword config and expansion example'
306 msg = 'hg keyword config and expansion example'
307 kwstatus = 'current'
307 kwstatus = 'current'
308 fn = 'demo.txt'
308 fn = 'demo.txt'
309 branchname = 'demobranch'
309 branchname = 'demobranch'
310 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
310 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
311 ui.note(_('creating temporary repo at %s\n') % tmpdir)
311 ui.note(_('creating temporary repo at %s\n') % tmpdir)
312 repo = localrepo.localrepository(ui, path=tmpdir, create=True)
312 repo = localrepo.localrepository(ui, path=tmpdir, create=True)
313 ui.setconfig('keyword', fn, '')
313 ui.setconfig('keyword', fn, '')
314 if args or opts.get('rcfile'):
314 if args or opts.get('rcfile'):
315 kwstatus = 'custom'
315 kwstatus = 'custom'
316 if opts.get('rcfile'):
316 if opts.get('rcfile'):
317 ui.readconfig(opts.get('rcfile'))
317 ui.readconfig(opts.get('rcfile'))
318 if opts.get('default'):
318 if opts.get('default'):
319 kwstatus = 'default'
319 kwstatus = 'default'
320 kwmaps = kwtemplater.templates
320 kwmaps = kwtemplater.templates
321 if ui.configitems('keywordmaps'):
321 if ui.configitems('keywordmaps'):
322 # override maps from optional rcfile
322 # override maps from optional rcfile
323 for k, v in kwmaps.iteritems():
323 for k, v in kwmaps.iteritems():
324 ui.setconfig('keywordmaps', k, v)
324 ui.setconfig('keywordmaps', k, v)
325 elif args:
325 elif args:
326 # simulate hgrc parsing
326 # simulate hgrc parsing
327 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
327 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
328 fp = repo.opener('hgrc', 'w')
328 fp = repo.opener('hgrc', 'w')
329 fp.writelines(rcmaps)
329 fp.writelines(rcmaps)
330 fp.close()
330 fp.close()
331 ui.readconfig(repo.join('hgrc'))
331 ui.readconfig(repo.join('hgrc'))
332 if not opts.get('default'):
332 if not opts.get('default'):
333 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
333 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
334 reposetup(ui, repo)
334 reposetup(ui, repo)
335 for k, v in ui.configitems('extensions'):
335 for k, v in ui.configitems('extensions'):
336 if k.endswith('keyword'):
336 if k.endswith('keyword'):
337 extension = '%s = %s' % (k, v)
337 extension = '%s = %s' % (k, v)
338 break
338 break
339 demostatus('config using %s keyword template maps' % kwstatus)
339 demostatus('config using %s keyword template maps' % kwstatus)
340 ui.write('[extensions]\n%s\n' % extension)
340 ui.write('[extensions]\n%s\n' % extension)
341 demoitems('keyword', ui.configitems('keyword'))
341 demoitems('keyword', ui.configitems('keyword'))
342 demoitems('keywordmaps', kwmaps.iteritems())
342 demoitems('keywordmaps', kwmaps.iteritems())
343 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
343 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
344 repo.wopener(fn, 'w').write(keywords)
344 repo.wopener(fn, 'w').write(keywords)
345 repo.add([fn])
345 repo.add([fn])
346 path = repo.wjoin(fn)
346 path = repo.wjoin(fn)
347 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
347 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
348 ui.note(keywords)
348 ui.note(keywords)
349 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
349 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
350 # silence branch command if not verbose
350 # silence branch command if not verbose
351 quiet = ui.quiet
351 quiet = ui.quiet
352 ui.quiet = not ui.verbose
352 ui.quiet = not ui.verbose
353 commands.branch(ui, repo, branchname)
353 commands.branch(ui, repo, branchname)
354 ui.quiet = quiet
354 ui.quiet = quiet
355 for name, cmd in ui.configitems('hooks'):
355 for name, cmd in ui.configitems('hooks'):
356 if name.split('.', 1)[0].find('commit') > -1:
356 if name.split('.', 1)[0].find('commit') > -1:
357 repo.ui.setconfig('hooks', name, '')
357 repo.ui.setconfig('hooks', name, '')
358 ui.note(_('unhooked all commit hooks\n'))
358 ui.note(_('unhooked all commit hooks\n'))
359 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
359 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
360 repo.commit(text=msg)
360 repo.commit(text=msg)
361 format = ui.verbose and ' in %s' % path or ''
361 format = ui.verbose and ' in %s' % path or ''
362 demostatus('%s keywords expanded%s' % (kwstatus, format))
362 demostatus('%s keywords expanded%s' % (kwstatus, format))
363 ui.write(repo.wread(fn))
363 ui.write(repo.wread(fn))
364 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
364 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
365 shutil.rmtree(tmpdir, ignore_errors=True)
365 shutil.rmtree(tmpdir, ignore_errors=True)
366
366
367 def expand(ui, repo, *pats, **opts):
367 def expand(ui, repo, *pats, **opts):
368 '''expand keywords in working directory
368 '''expand keywords in working directory
369
369
370 Run after (re)enabling keyword expansion.
370 Run after (re)enabling keyword expansion.
371
371
372 kwexpand refuses to run if given files contain local changes.
372 kwexpand refuses to run if given files contain local changes.
373 '''
373 '''
374 # 3rd argument sets expansion to True
374 # 3rd argument sets expansion to True
375 _kwfwrite(ui, repo, True, *pats, **opts)
375 _kwfwrite(ui, repo, True, *pats, **opts)
376
376
377 def files(ui, repo, *pats, **opts):
377 def files(ui, repo, *pats, **opts):
378 '''print files currently configured for keyword expansion
378 '''print files currently configured for keyword expansion
379
379
380 Crosscheck which files in working directory are potential targets for
380 Crosscheck which files in working directory are potential targets for
381 keyword expansion.
381 keyword expansion.
382 That is, files matched by [keyword] config patterns but not symlinks.
382 That is, files matched by [keyword] config patterns but not symlinks.
383 '''
383 '''
384 status = _status(ui, repo, *pats, **opts)
384 status = _status(ui, repo, *pats, **opts)
385 modified, added, removed, deleted, unknown, ignored, clean = status
385 modified, added, removed, deleted, unknown, ignored, clean = status
386 files = modified + added + clean
386 files = modified + added + clean
387 if opts.get('untracked'):
387 if opts.get('untracked'):
388 files += unknown
388 files += unknown
389 files.sort()
389 files.sort()
390 kwfiles = [f for f in files if _iskwfile(f, repo._link)]
390 wctx = repo.workingctx()
391 islink = lambda p: 'l' in wctx.fileflags(p)
392 kwfiles = [f for f in files if _iskwfile(f, islink)]
391 cwd = pats and repo.getcwd() or ''
393 cwd = pats and repo.getcwd() or ''
392 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
394 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
393 if opts.get('all') or opts.get('ignore'):
395 if opts.get('all') or opts.get('ignore'):
394 kwfstats += (('I', [f for f in files if f not in kwfiles]),)
396 kwfstats += (('I', [f for f in files if f not in kwfiles]),)
395 for char, filenames in kwfstats:
397 for char, filenames in kwfstats:
396 format = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
398 format = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
397 for f in filenames:
399 for f in filenames:
398 ui.write(format % repo.pathto(f, cwd))
400 ui.write(format % repo.pathto(f, cwd))
399
401
400 def shrink(ui, repo, *pats, **opts):
402 def shrink(ui, repo, *pats, **opts):
401 '''revert expanded keywords in working directory
403 '''revert expanded keywords in working directory
402
404
403 Run before changing/disabling active keywords
405 Run before changing/disabling active keywords
404 or if you experience problems with "hg import" or "hg merge".
406 or if you experience problems with "hg import" or "hg merge".
405
407
406 kwshrink refuses to run if given files contain local changes.
408 kwshrink refuses to run if given files contain local changes.
407 '''
409 '''
408 # 3rd argument sets expansion to False
410 # 3rd argument sets expansion to False
409 _kwfwrite(ui, repo, False, *pats, **opts)
411 _kwfwrite(ui, repo, False, *pats, **opts)
410
412
411
413
412 def reposetup(ui, repo):
414 def reposetup(ui, repo):
413 '''Sets up repo as kwrepo for keyword substitution.
415 '''Sets up repo as kwrepo for keyword substitution.
414 Overrides file method to return kwfilelog instead of filelog
416 Overrides file method to return kwfilelog instead of filelog
415 if file matches user configuration.
417 if file matches user configuration.
416 Wraps commit to overwrite configured files with updated
418 Wraps commit to overwrite configured files with updated
417 keyword substitutions.
419 keyword substitutions.
418 This is done for local repos only, and only if there are
420 This is done for local repos only, and only if there are
419 files configured at all for keyword substitution.'''
421 files configured at all for keyword substitution.'''
420
422
421 global _kwtemplater
423 global _kwtemplater
422 hgcmd, hgcmdopts = _cmd, _cmdoptions
424 hgcmd, hgcmdopts = _cmd, _cmdoptions
423
425
424 try:
426 try:
425 if (not repo.local() or hgcmd in nokwcommands.split()
427 if (not repo.local() or hgcmd in nokwcommands.split()
426 or '.hg' in repo.root.split(os.sep)
428 or '.hg' in repo.root.split(os.sep)
427 or repo._url.startswith('bundle:')):
429 or repo._url.startswith('bundle:')):
428 return
430 return
429 except AttributeError:
431 except AttributeError:
430 pass
432 pass
431
433
432 inc, exc = [], ['.hg*']
434 inc, exc = [], ['.hg*']
433 for pat, opt in ui.configitems('keyword'):
435 for pat, opt in ui.configitems('keyword'):
434 if opt != 'ignore':
436 if opt != 'ignore':
435 inc.append(pat)
437 inc.append(pat)
436 else:
438 else:
437 exc.append(pat)
439 exc.append(pat)
438 if not inc:
440 if not inc:
439 return
441 return
440
442
441 if hgcmd == 'diff':
443 if hgcmd == 'diff':
442 # only expand if comparing against working dir
444 # only expand if comparing against working dir
443 node1, node2 = cmdutil.revpair(repo, hgcmdopts.get('rev'))
445 node1, node2 = cmdutil.revpair(repo, hgcmdopts.get('rev'))
444 if node2 is not None:
446 if node2 is not None:
445 return
447 return
446 # shrink if rev is not current node
448 # shrink if rev is not current node
447 if node1 is not None and node1 != repo.changectx().node():
449 if node1 is not None and node1 != repo.changectx().node():
448 hgcmd = 'diff1'
450 hgcmd = 'diff1'
449
451
450 restrict = hgcmd in restricted.split()
452 restrict = hgcmd in restricted.split()
451 _kwtemplater = kwtemplater(ui, repo, inc, exc, restrict)
453 _kwtemplater = kwtemplater(ui, repo, inc, exc, restrict)
452
454
453 class kwrepo(repo.__class__):
455 class kwrepo(repo.__class__):
454 def file(self, f, kwmatch=False):
456 def file(self, f, kwmatch=False):
455 if f[0] == '/':
457 if f[0] == '/':
456 f = f[1:]
458 f = f[1:]
457 if kwmatch or _kwtemplater.matcher(f):
459 if kwmatch or _kwtemplater.matcher(f):
458 return kwfilelog(self.sopener, f)
460 return kwfilelog(self.sopener, f)
459 return filelog.filelog(self.sopener, f)
461 return filelog.filelog(self.sopener, f)
460
462
461 def wread(self, filename):
463 def wread(self, filename):
462 data = super(kwrepo, self).wread(filename)
464 data = super(kwrepo, self).wread(filename)
463 if restrict and _kwtemplater.matcher(filename):
465 if restrict and _kwtemplater.matcher(filename):
464 return _kwtemplater.shrink(data)
466 return _kwtemplater.shrink(data)
465 return data
467 return data
466
468
467 def commit(self, files=None, text='', user=None, date=None,
469 def commit(self, files=None, text='', user=None, date=None,
468 match=util.always, force=False, force_editor=False,
470 match=util.always, force=False, force_editor=False,
469 p1=None, p2=None, extra={}, empty_ok=False):
471 p1=None, p2=None, extra={}, empty_ok=False):
470 wlock = lock = None
472 wlock = lock = None
471 _p1 = _p2 = None
473 _p1 = _p2 = None
472 try:
474 try:
473 wlock = self.wlock()
475 wlock = self.wlock()
474 lock = self.lock()
476 lock = self.lock()
475 # store and postpone commit hooks
477 # store and postpone commit hooks
476 commithooks = {}
478 commithooks = {}
477 for name, cmd in ui.configitems('hooks'):
479 for name, cmd in ui.configitems('hooks'):
478 if name.split('.', 1)[0] == 'commit':
480 if name.split('.', 1)[0] == 'commit':
479 commithooks[name] = cmd
481 commithooks[name] = cmd
480 ui.setconfig('hooks', name, None)
482 ui.setconfig('hooks', name, None)
481 if commithooks:
483 if commithooks:
482 # store parents for commit hook environment
484 # store parents for commit hook environment
483 if p1 is None:
485 if p1 is None:
484 _p1, _p2 = repo.dirstate.parents()
486 _p1, _p2 = repo.dirstate.parents()
485 else:
487 else:
486 _p1, _p2 = p1, p2 or nullid
488 _p1, _p2 = p1, p2 or nullid
487 _p1 = hex(_p1)
489 _p1 = hex(_p1)
488 if _p2 == nullid:
490 if _p2 == nullid:
489 _p2 = ''
491 _p2 = ''
490 else:
492 else:
491 _p2 = hex(_p2)
493 _p2 = hex(_p2)
492
494
493 node = super(kwrepo,
495 node = super(kwrepo,
494 self).commit(files=files, text=text, user=user,
496 self).commit(files=files, text=text, user=user,
495 date=date, match=match, force=force,
497 date=date, match=match, force=force,
496 force_editor=force_editor,
498 force_editor=force_editor,
497 p1=p1, p2=p2, extra=extra,
499 p1=p1, p2=p2, extra=extra,
498 empty_ok=empty_ok)
500 empty_ok=empty_ok)
499
501
500 # restore commit hooks
502 # restore commit hooks
501 for name, cmd in commithooks.iteritems():
503 for name, cmd in commithooks.iteritems():
502 ui.setconfig('hooks', name, cmd)
504 ui.setconfig('hooks', name, cmd)
503 if node is not None:
505 if node is not None:
504 _overwrite(ui, self, node=node)
506 _overwrite(ui, self, node=node)
505 repo.hook('commit', node=node, parent1=_p1, parent2=_p2)
507 repo.hook('commit', node=node, parent1=_p1, parent2=_p2)
506 return node
508 return node
507 finally:
509 finally:
508 del wlock, lock
510 del wlock, lock
509
511
510 repo.__class__ = kwrepo
512 repo.__class__ = kwrepo
511 patch.patchfile.__init__ = _kwpatchfile_init
513 patch.patchfile.__init__ = _kwpatchfile_init
512
514
513
515
514 cmdtable = {
516 cmdtable = {
515 'kwdemo':
517 'kwdemo':
516 (demo,
518 (demo,
517 [('d', 'default', None, _('show default keyword template maps')),
519 [('d', 'default', None, _('show default keyword template maps')),
518 ('f', 'rcfile', [], _('read maps from rcfile'))],
520 ('f', 'rcfile', [], _('read maps from rcfile'))],
519 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
521 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
520 'kwexpand': (expand, commands.walkopts,
522 'kwexpand': (expand, commands.walkopts,
521 _('hg kwexpand [OPTION]... [FILE]...')),
523 _('hg kwexpand [OPTION]... [FILE]...')),
522 'kwfiles':
524 'kwfiles':
523 (files,
525 (files,
524 [('a', 'all', None, _('show keyword status flags of all files')),
526 [('a', 'all', None, _('show keyword status flags of all files')),
525 ('i', 'ignore', None, _('show files excluded from expansion')),
527 ('i', 'ignore', None, _('show files excluded from expansion')),
526 ('u', 'untracked', None, _('additionally show untracked files')),
528 ('u', 'untracked', None, _('additionally show untracked files')),
527 ] + commands.walkopts,
529 ] + commands.walkopts,
528 _('hg kwfiles [OPTION]... [FILE]...')),
530 _('hg kwfiles [OPTION]... [FILE]...')),
529 'kwshrink': (shrink, commands.walkopts,
531 'kwshrink': (shrink, commands.walkopts,
530 _('hg kwshrink [OPTION]... [FILE]...')),
532 _('hg kwshrink [OPTION]... [FILE]...')),
531 }
533 }
General Comments 0
You need to be logged in to leave comments. Login now