##// END OF EJS Templates
keyword: move common code out of commit condition
Christian Ebert -
r7378:8dde2756 default
parent child Browse files
Show More
@@ -1,538 +1,536 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, dispatch, filelog, revlog, extensions
81 from mercurial import commands, cmdutil, dispatch, filelog, revlog, extensions
82 from mercurial import patch, localrepo, templater, templatefilters, util
82 from mercurial import patch, localrepo, templater, templatefilters, util
83 from mercurial.hgweb import webcommands
83 from mercurial.hgweb import webcommands
84 from mercurial.node import nullid, hex
84 from mercurial.node import nullid, hex
85 from mercurial.i18n import _
85 from mercurial.i18n import _
86 import re, shutil, tempfile, time
86 import re, shutil, tempfile, time
87
87
88 commands.optionalrepo += ' kwdemo'
88 commands.optionalrepo += ' kwdemo'
89
89
90 # hg commands that do not act on keywords
90 # hg commands that do not act on keywords
91 nokwcommands = ('add addremove annotate bundle copy export grep incoming init'
91 nokwcommands = ('add addremove annotate bundle copy export grep incoming init'
92 ' log outgoing push rename rollback tip verify'
92 ' log outgoing push rename rollback tip verify'
93 ' convert email glog')
93 ' convert email glog')
94
94
95 # hg commands that trigger expansion only when writing to working dir,
95 # hg commands that trigger expansion only when writing to working dir,
96 # not when reading filelog, and unexpand when reading from working dir
96 # not when reading filelog, and unexpand when reading from working dir
97 restricted = 'merge record resolve qfold qimport qnew qpush qrefresh qrecord'
97 restricted = 'merge record resolve qfold qimport qnew qpush qrefresh qrecord'
98
98
99 def utcdate(date):
99 def utcdate(date):
100 '''Returns hgdate in cvs-like UTC format.'''
100 '''Returns hgdate in cvs-like UTC format.'''
101 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
101 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
102
102
103 # make keyword tools accessible
103 # make keyword tools accessible
104 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
104 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
105
105
106
106
107 class kwtemplater(object):
107 class kwtemplater(object):
108 '''
108 '''
109 Sets up keyword templates, corresponding keyword regex, and
109 Sets up keyword templates, corresponding keyword regex, and
110 provides keyword substitution functions.
110 provides keyword substitution functions.
111 '''
111 '''
112 templates = {
112 templates = {
113 'Revision': '{node|short}',
113 'Revision': '{node|short}',
114 'Author': '{author|user}',
114 'Author': '{author|user}',
115 'Date': '{date|utcdate}',
115 'Date': '{date|utcdate}',
116 'RCSFile': '{file|basename},v',
116 'RCSFile': '{file|basename},v',
117 'Source': '{root}/{file},v',
117 'Source': '{root}/{file},v',
118 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
118 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
119 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
119 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
120 }
120 }
121
121
122 def __init__(self, ui, repo):
122 def __init__(self, ui, repo):
123 self.ui = ui
123 self.ui = ui
124 self.repo = repo
124 self.repo = repo
125 self.matcher = util.matcher(repo.root,
125 self.matcher = util.matcher(repo.root,
126 inc=kwtools['inc'], exc=kwtools['exc'])[1]
126 inc=kwtools['inc'], exc=kwtools['exc'])[1]
127 self.restrict = kwtools['hgcmd'] in restricted.split()
127 self.restrict = kwtools['hgcmd'] in restricted.split()
128
128
129 kwmaps = self.ui.configitems('keywordmaps')
129 kwmaps = self.ui.configitems('keywordmaps')
130 if kwmaps: # override default templates
130 if kwmaps: # override default templates
131 kwmaps = [(k, templater.parsestring(v, False))
131 kwmaps = [(k, templater.parsestring(v, False))
132 for (k, v) in kwmaps]
132 for (k, v) in kwmaps]
133 self.templates = dict(kwmaps)
133 self.templates = dict(kwmaps)
134 escaped = map(re.escape, self.templates.keys())
134 escaped = map(re.escape, self.templates.keys())
135 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
135 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
136 self.re_kw = re.compile(kwpat)
136 self.re_kw = re.compile(kwpat)
137
137
138 templatefilters.filters['utcdate'] = utcdate
138 templatefilters.filters['utcdate'] = utcdate
139 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
139 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
140 False, '', False)
140 False, '', False)
141
141
142 def substitute(self, data, path, ctx, subfunc):
142 def substitute(self, data, path, ctx, subfunc):
143 '''Replaces keywords in data with expanded template.'''
143 '''Replaces keywords in data with expanded template.'''
144 def kwsub(mobj):
144 def kwsub(mobj):
145 kw = mobj.group(1)
145 kw = mobj.group(1)
146 self.ct.use_template(self.templates[kw])
146 self.ct.use_template(self.templates[kw])
147 self.ui.pushbuffer()
147 self.ui.pushbuffer()
148 self.ct.show(ctx, root=self.repo.root, file=path)
148 self.ct.show(ctx, root=self.repo.root, file=path)
149 ekw = templatefilters.firstline(self.ui.popbuffer())
149 ekw = templatefilters.firstline(self.ui.popbuffer())
150 return '$%s: %s $' % (kw, ekw)
150 return '$%s: %s $' % (kw, ekw)
151 return subfunc(kwsub, data)
151 return subfunc(kwsub, data)
152
152
153 def expand(self, path, node, data):
153 def expand(self, path, node, data):
154 '''Returns data with keywords expanded.'''
154 '''Returns data with keywords expanded.'''
155 if not self.restrict and self.matcher(path) and not util.binary(data):
155 if not self.restrict and self.matcher(path) and not util.binary(data):
156 ctx = self.repo.filectx(path, fileid=node).changectx()
156 ctx = self.repo.filectx(path, fileid=node).changectx()
157 return self.substitute(data, path, ctx, self.re_kw.sub)
157 return self.substitute(data, path, ctx, self.re_kw.sub)
158 return data
158 return data
159
159
160 def iskwfile(self, path, flagfunc):
160 def iskwfile(self, path, flagfunc):
161 '''Returns true if path matches [keyword] pattern
161 '''Returns true if path matches [keyword] pattern
162 and is not a symbolic link.
162 and is not a symbolic link.
163 Caveat: localrepository._link fails on Windows.'''
163 Caveat: localrepository._link fails on Windows.'''
164 return self.matcher(path) and not 'l' in flagfunc(path)
164 return self.matcher(path) and not 'l' in flagfunc(path)
165
165
166 def overwrite(self, node, expand, files):
166 def overwrite(self, node, expand, files):
167 '''Overwrites selected files expanding/shrinking keywords.'''
167 '''Overwrites selected files expanding/shrinking keywords.'''
168 ctx = self.repo[node]
169 mf = ctx.manifest()
168 if node is not None: # commit
170 if node is not None: # commit
169 ctx = self.repo[node]
170 mf = ctx.manifest()
171 files = [f for f in ctx.files() if f in mf]
171 files = [f for f in ctx.files() if f in mf]
172 notify = self.ui.debug
172 notify = self.ui.debug
173 else: # kwexpand/kwshrink
173 else: # kwexpand/kwshrink
174 ctx = self.repo[None]
175 mf = ctx.manifest()
176 notify = self.ui.note
174 notify = self.ui.note
177 candidates = [f for f in files if self.iskwfile(f, ctx.flags)]
175 candidates = [f for f in files if self.iskwfile(f, ctx.flags)]
178 if candidates:
176 if candidates:
179 self.restrict = True # do not expand when reading
177 self.restrict = True # do not expand when reading
180 action = expand and 'expanding' or 'shrinking'
178 action = expand and 'expanding' or 'shrinking'
181 for f in candidates:
179 for f in candidates:
182 fp = self.repo.file(f)
180 fp = self.repo.file(f)
183 data = fp.read(mf[f])
181 data = fp.read(mf[f])
184 if util.binary(data):
182 if util.binary(data):
185 continue
183 continue
186 if expand:
184 if expand:
187 if node is None:
185 if node is None:
188 ctx = self.repo.filectx(f, fileid=mf[f]).changectx()
186 ctx = self.repo.filectx(f, fileid=mf[f]).changectx()
189 data, found = self.substitute(data, f, ctx,
187 data, found = self.substitute(data, f, ctx,
190 self.re_kw.subn)
188 self.re_kw.subn)
191 else:
189 else:
192 found = self.re_kw.search(data)
190 found = self.re_kw.search(data)
193 if found:
191 if found:
194 notify(_('overwriting %s %s keywords\n') % (f, action))
192 notify(_('overwriting %s %s keywords\n') % (f, action))
195 self.repo.wwrite(f, data, mf.flags(f))
193 self.repo.wwrite(f, data, mf.flags(f))
196 self.repo.dirstate.normal(f)
194 self.repo.dirstate.normal(f)
197 self.restrict = False
195 self.restrict = False
198
196
199 def shrinktext(self, text):
197 def shrinktext(self, text):
200 '''Unconditionally removes all keyword substitutions from text.'''
198 '''Unconditionally removes all keyword substitutions from text.'''
201 return self.re_kw.sub(r'$\1$', text)
199 return self.re_kw.sub(r'$\1$', text)
202
200
203 def shrink(self, fname, text):
201 def shrink(self, fname, text):
204 '''Returns text with all keyword substitutions removed.'''
202 '''Returns text with all keyword substitutions removed.'''
205 if self.matcher(fname) and not util.binary(text):
203 if self.matcher(fname) and not util.binary(text):
206 return self.shrinktext(text)
204 return self.shrinktext(text)
207 return text
205 return text
208
206
209 def shrinklines(self, fname, lines):
207 def shrinklines(self, fname, lines):
210 '''Returns lines with keyword substitutions removed.'''
208 '''Returns lines with keyword substitutions removed.'''
211 if self.matcher(fname):
209 if self.matcher(fname):
212 text = ''.join(lines)
210 text = ''.join(lines)
213 if not util.binary(text):
211 if not util.binary(text):
214 return self.shrinktext(text).splitlines(True)
212 return self.shrinktext(text).splitlines(True)
215 return lines
213 return lines
216
214
217 def wread(self, fname, data):
215 def wread(self, fname, data):
218 '''If in restricted mode returns data read from wdir with
216 '''If in restricted mode returns data read from wdir with
219 keyword substitutions removed.'''
217 keyword substitutions removed.'''
220 return self.restrict and self.shrink(fname, data) or data
218 return self.restrict and self.shrink(fname, data) or data
221
219
222 class kwfilelog(filelog.filelog):
220 class kwfilelog(filelog.filelog):
223 '''
221 '''
224 Subclass of filelog to hook into its read, add, cmp methods.
222 Subclass of filelog to hook into its read, add, cmp methods.
225 Keywords are "stored" unexpanded, and processed on reading.
223 Keywords are "stored" unexpanded, and processed on reading.
226 '''
224 '''
227 def __init__(self, opener, kwt, path):
225 def __init__(self, opener, kwt, path):
228 super(kwfilelog, self).__init__(opener, path)
226 super(kwfilelog, self).__init__(opener, path)
229 self.kwt = kwt
227 self.kwt = kwt
230 self.path = path
228 self.path = path
231
229
232 def read(self, node):
230 def read(self, node):
233 '''Expands keywords when reading filelog.'''
231 '''Expands keywords when reading filelog.'''
234 data = super(kwfilelog, self).read(node)
232 data = super(kwfilelog, self).read(node)
235 return self.kwt.expand(self.path, node, data)
233 return self.kwt.expand(self.path, node, data)
236
234
237 def add(self, text, meta, tr, link, p1=None, p2=None):
235 def add(self, text, meta, tr, link, p1=None, p2=None):
238 '''Removes keyword substitutions when adding to filelog.'''
236 '''Removes keyword substitutions when adding to filelog.'''
239 text = self.kwt.shrink(self.path, text)
237 text = self.kwt.shrink(self.path, text)
240 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
238 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
241
239
242 def cmp(self, node, text):
240 def cmp(self, node, text):
243 '''Removes keyword substitutions for comparison.'''
241 '''Removes keyword substitutions for comparison.'''
244 text = self.kwt.shrink(self.path, text)
242 text = self.kwt.shrink(self.path, text)
245 if self.renamed(node):
243 if self.renamed(node):
246 t2 = super(kwfilelog, self).read(node)
244 t2 = super(kwfilelog, self).read(node)
247 return t2 != text
245 return t2 != text
248 return revlog.revlog.cmp(self, node, text)
246 return revlog.revlog.cmp(self, node, text)
249
247
250 def _status(ui, repo, kwt, unknown, *pats, **opts):
248 def _status(ui, repo, kwt, unknown, *pats, **opts):
251 '''Bails out if [keyword] configuration is not active.
249 '''Bails out if [keyword] configuration is not active.
252 Returns status of working directory.'''
250 Returns status of working directory.'''
253 if kwt:
251 if kwt:
254 matcher = cmdutil.match(repo, pats, opts)
252 matcher = cmdutil.match(repo, pats, opts)
255 return repo.status(match=matcher, unknown=unknown, clean=True)
253 return repo.status(match=matcher, unknown=unknown, clean=True)
256 if ui.configitems('keyword'):
254 if ui.configitems('keyword'):
257 raise util.Abort(_('[keyword] patterns cannot match'))
255 raise util.Abort(_('[keyword] patterns cannot match'))
258 raise util.Abort(_('no [keyword] patterns configured'))
256 raise util.Abort(_('no [keyword] patterns configured'))
259
257
260 def _kwfwrite(ui, repo, expand, *pats, **opts):
258 def _kwfwrite(ui, repo, expand, *pats, **opts):
261 '''Selects files and passes them to kwtemplater.overwrite.'''
259 '''Selects files and passes them to kwtemplater.overwrite.'''
262 if repo.dirstate.parents()[1] != nullid:
260 if repo.dirstate.parents()[1] != nullid:
263 raise util.Abort(_('outstanding uncommitted merge'))
261 raise util.Abort(_('outstanding uncommitted merge'))
264 kwt = kwtools['templater']
262 kwt = kwtools['templater']
265 status = _status(ui, repo, kwt, False, *pats, **opts)
263 status = _status(ui, repo, kwt, False, *pats, **opts)
266 modified, added, removed, deleted = status[:4]
264 modified, added, removed, deleted = status[:4]
267 if modified or added or removed or deleted:
265 if modified or added or removed or deleted:
268 raise util.Abort(_('outstanding uncommitted changes'))
266 raise util.Abort(_('outstanding uncommitted changes'))
269 wlock = lock = None
267 wlock = lock = None
270 try:
268 try:
271 wlock = repo.wlock()
269 wlock = repo.wlock()
272 lock = repo.lock()
270 lock = repo.lock()
273 kwt.overwrite(None, expand, status[6])
271 kwt.overwrite(None, expand, status[6])
274 finally:
272 finally:
275 del wlock, lock
273 del wlock, lock
276
274
277
275
278 def demo(ui, repo, *args, **opts):
276 def demo(ui, repo, *args, **opts):
279 '''print [keywordmaps] configuration and an expansion example
277 '''print [keywordmaps] configuration and an expansion example
280
278
281 Show current, custom, or default keyword template maps
279 Show current, custom, or default keyword template maps
282 and their expansion.
280 and their expansion.
283
281
284 Extend current configuration by specifying maps as arguments
282 Extend current configuration by specifying maps as arguments
285 and optionally by reading from an additional hgrc file.
283 and optionally by reading from an additional hgrc file.
286
284
287 Override current keyword template maps with "default" option.
285 Override current keyword template maps with "default" option.
288 '''
286 '''
289 def demostatus(stat):
287 def demostatus(stat):
290 ui.status(_('\n\t%s\n') % stat)
288 ui.status(_('\n\t%s\n') % stat)
291
289
292 def demoitems(section, items):
290 def demoitems(section, items):
293 ui.write('[%s]\n' % section)
291 ui.write('[%s]\n' % section)
294 for k, v in items:
292 for k, v in items:
295 ui.write('%s = %s\n' % (k, v))
293 ui.write('%s = %s\n' % (k, v))
296
294
297 msg = 'hg keyword config and expansion example'
295 msg = 'hg keyword config and expansion example'
298 kwstatus = 'current'
296 kwstatus = 'current'
299 fn = 'demo.txt'
297 fn = 'demo.txt'
300 branchname = 'demobranch'
298 branchname = 'demobranch'
301 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
299 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
302 ui.note(_('creating temporary repo at %s\n') % tmpdir)
300 ui.note(_('creating temporary repo at %s\n') % tmpdir)
303 repo = localrepo.localrepository(ui, tmpdir, True)
301 repo = localrepo.localrepository(ui, tmpdir, True)
304 ui.setconfig('keyword', fn, '')
302 ui.setconfig('keyword', fn, '')
305 if args or opts.get('rcfile'):
303 if args or opts.get('rcfile'):
306 kwstatus = 'custom'
304 kwstatus = 'custom'
307 if opts.get('rcfile'):
305 if opts.get('rcfile'):
308 ui.readconfig(opts.get('rcfile'))
306 ui.readconfig(opts.get('rcfile'))
309 if opts.get('default'):
307 if opts.get('default'):
310 kwstatus = 'default'
308 kwstatus = 'default'
311 kwmaps = kwtemplater.templates
309 kwmaps = kwtemplater.templates
312 if ui.configitems('keywordmaps'):
310 if ui.configitems('keywordmaps'):
313 # override maps from optional rcfile
311 # override maps from optional rcfile
314 for k, v in kwmaps.iteritems():
312 for k, v in kwmaps.iteritems():
315 ui.setconfig('keywordmaps', k, v)
313 ui.setconfig('keywordmaps', k, v)
316 elif args:
314 elif args:
317 # simulate hgrc parsing
315 # simulate hgrc parsing
318 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
316 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
319 fp = repo.opener('hgrc', 'w')
317 fp = repo.opener('hgrc', 'w')
320 fp.writelines(rcmaps)
318 fp.writelines(rcmaps)
321 fp.close()
319 fp.close()
322 ui.readconfig(repo.join('hgrc'))
320 ui.readconfig(repo.join('hgrc'))
323 if not opts.get('default'):
321 if not opts.get('default'):
324 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
322 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
325 uisetup(ui)
323 uisetup(ui)
326 reposetup(ui, repo)
324 reposetup(ui, repo)
327 for k, v in ui.configitems('extensions'):
325 for k, v in ui.configitems('extensions'):
328 if k.endswith('keyword'):
326 if k.endswith('keyword'):
329 extension = '%s = %s' % (k, v)
327 extension = '%s = %s' % (k, v)
330 break
328 break
331 demostatus('config using %s keyword template maps' % kwstatus)
329 demostatus('config using %s keyword template maps' % kwstatus)
332 ui.write('[extensions]\n%s\n' % extension)
330 ui.write('[extensions]\n%s\n' % extension)
333 demoitems('keyword', ui.configitems('keyword'))
331 demoitems('keyword', ui.configitems('keyword'))
334 demoitems('keywordmaps', kwmaps.iteritems())
332 demoitems('keywordmaps', kwmaps.iteritems())
335 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
333 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
336 repo.wopener(fn, 'w').write(keywords)
334 repo.wopener(fn, 'w').write(keywords)
337 repo.add([fn])
335 repo.add([fn])
338 path = repo.wjoin(fn)
336 path = repo.wjoin(fn)
339 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
337 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
340 ui.note(keywords)
338 ui.note(keywords)
341 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
339 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
342 # silence branch command if not verbose
340 # silence branch command if not verbose
343 quiet = ui.quiet
341 quiet = ui.quiet
344 ui.quiet = not ui.verbose
342 ui.quiet = not ui.verbose
345 commands.branch(ui, repo, branchname)
343 commands.branch(ui, repo, branchname)
346 ui.quiet = quiet
344 ui.quiet = quiet
347 for name, cmd in ui.configitems('hooks'):
345 for name, cmd in ui.configitems('hooks'):
348 if name.split('.', 1)[0].find('commit') > -1:
346 if name.split('.', 1)[0].find('commit') > -1:
349 repo.ui.setconfig('hooks', name, '')
347 repo.ui.setconfig('hooks', name, '')
350 ui.note(_('unhooked all commit hooks\n'))
348 ui.note(_('unhooked all commit hooks\n'))
351 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
349 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
352 repo.commit(text=msg)
350 repo.commit(text=msg)
353 format = ui.verbose and ' in %s' % path or ''
351 format = ui.verbose and ' in %s' % path or ''
354 demostatus('%s keywords expanded%s' % (kwstatus, format))
352 demostatus('%s keywords expanded%s' % (kwstatus, format))
355 ui.write(repo.wread(fn))
353 ui.write(repo.wread(fn))
356 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
354 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
357 shutil.rmtree(tmpdir, ignore_errors=True)
355 shutil.rmtree(tmpdir, ignore_errors=True)
358
356
359 def expand(ui, repo, *pats, **opts):
357 def expand(ui, repo, *pats, **opts):
360 '''expand keywords in working directory
358 '''expand keywords in working directory
361
359
362 Run after (re)enabling keyword expansion.
360 Run after (re)enabling keyword expansion.
363
361
364 kwexpand refuses to run if given files contain local changes.
362 kwexpand refuses to run if given files contain local changes.
365 '''
363 '''
366 # 3rd argument sets expansion to True
364 # 3rd argument sets expansion to True
367 _kwfwrite(ui, repo, True, *pats, **opts)
365 _kwfwrite(ui, repo, True, *pats, **opts)
368
366
369 def files(ui, repo, *pats, **opts):
367 def files(ui, repo, *pats, **opts):
370 '''print files currently configured for keyword expansion
368 '''print files currently configured for keyword expansion
371
369
372 Crosscheck which files in working directory are potential targets for
370 Crosscheck which files in working directory are potential targets for
373 keyword expansion.
371 keyword expansion.
374 That is, files matched by [keyword] config patterns but not symlinks.
372 That is, files matched by [keyword] config patterns but not symlinks.
375 '''
373 '''
376 kwt = kwtools['templater']
374 kwt = kwtools['templater']
377 status = _status(ui, repo, kwt, opts.get('untracked'), *pats, **opts)
375 status = _status(ui, repo, kwt, opts.get('untracked'), *pats, **opts)
378 modified, added, removed, deleted, unknown, ignored, clean = status
376 modified, added, removed, deleted, unknown, ignored, clean = status
379 files = util.sort(modified + added + clean + unknown)
377 files = util.sort(modified + added + clean + unknown)
380 wctx = repo[None]
378 wctx = repo[None]
381 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
379 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
382 cwd = pats and repo.getcwd() or ''
380 cwd = pats and repo.getcwd() or ''
383 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
381 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
384 if opts.get('all') or opts.get('ignore'):
382 if opts.get('all') or opts.get('ignore'):
385 kwfstats += (('I', [f for f in files if f not in kwfiles]),)
383 kwfstats += (('I', [f for f in files if f not in kwfiles]),)
386 for char, filenames in kwfstats:
384 for char, filenames in kwfstats:
387 format = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
385 format = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
388 for f in filenames:
386 for f in filenames:
389 ui.write(format % repo.pathto(f, cwd))
387 ui.write(format % repo.pathto(f, cwd))
390
388
391 def shrink(ui, repo, *pats, **opts):
389 def shrink(ui, repo, *pats, **opts):
392 '''revert expanded keywords in working directory
390 '''revert expanded keywords in working directory
393
391
394 Run before changing/disabling active keywords
392 Run before changing/disabling active keywords
395 or if you experience problems with "hg import" or "hg merge".
393 or if you experience problems with "hg import" or "hg merge".
396
394
397 kwshrink refuses to run if given files contain local changes.
395 kwshrink refuses to run if given files contain local changes.
398 '''
396 '''
399 # 3rd argument sets expansion to False
397 # 3rd argument sets expansion to False
400 _kwfwrite(ui, repo, False, *pats, **opts)
398 _kwfwrite(ui, repo, False, *pats, **opts)
401
399
402
400
403 def uisetup(ui):
401 def uisetup(ui):
404 '''Collects [keyword] config in kwtools.
402 '''Collects [keyword] config in kwtools.
405 Monkeypatches dispatch._parse if needed.'''
403 Monkeypatches dispatch._parse if needed.'''
406
404
407 for pat, opt in ui.configitems('keyword'):
405 for pat, opt in ui.configitems('keyword'):
408 if opt != 'ignore':
406 if opt != 'ignore':
409 kwtools['inc'].append(pat)
407 kwtools['inc'].append(pat)
410 else:
408 else:
411 kwtools['exc'].append(pat)
409 kwtools['exc'].append(pat)
412
410
413 if kwtools['inc']:
411 if kwtools['inc']:
414 def kwdispatch_parse(orig, ui, args):
412 def kwdispatch_parse(orig, ui, args):
415 '''Monkeypatch dispatch._parse to obtain running hg command.'''
413 '''Monkeypatch dispatch._parse to obtain running hg command.'''
416 cmd, func, args, options, cmdoptions = orig(ui, args)
414 cmd, func, args, options, cmdoptions = orig(ui, args)
417 kwtools['hgcmd'] = cmd
415 kwtools['hgcmd'] = cmd
418 return cmd, func, args, options, cmdoptions
416 return cmd, func, args, options, cmdoptions
419
417
420 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
418 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
421
419
422 def reposetup(ui, repo):
420 def reposetup(ui, repo):
423 '''Sets up repo as kwrepo for keyword substitution.
421 '''Sets up repo as kwrepo for keyword substitution.
424 Overrides file method to return kwfilelog instead of filelog
422 Overrides file method to return kwfilelog instead of filelog
425 if file matches user configuration.
423 if file matches user configuration.
426 Wraps commit to overwrite configured files with updated
424 Wraps commit to overwrite configured files with updated
427 keyword substitutions.
425 keyword substitutions.
428 Monkeypatches patch and webcommands.'''
426 Monkeypatches patch and webcommands.'''
429
427
430 try:
428 try:
431 if (not repo.local() or not kwtools['inc']
429 if (not repo.local() or not kwtools['inc']
432 or kwtools['hgcmd'] in nokwcommands.split()
430 or kwtools['hgcmd'] in nokwcommands.split()
433 or '.hg' in util.splitpath(repo.root)
431 or '.hg' in util.splitpath(repo.root)
434 or repo._url.startswith('bundle:')):
432 or repo._url.startswith('bundle:')):
435 return
433 return
436 except AttributeError:
434 except AttributeError:
437 pass
435 pass
438
436
439 kwtools['templater'] = kwt = kwtemplater(ui, repo)
437 kwtools['templater'] = kwt = kwtemplater(ui, repo)
440
438
441 class kwrepo(repo.__class__):
439 class kwrepo(repo.__class__):
442 def file(self, f):
440 def file(self, f):
443 if f[0] == '/':
441 if f[0] == '/':
444 f = f[1:]
442 f = f[1:]
445 return kwfilelog(self.sopener, kwt, f)
443 return kwfilelog(self.sopener, kwt, f)
446
444
447 def wread(self, filename):
445 def wread(self, filename):
448 data = super(kwrepo, self).wread(filename)
446 data = super(kwrepo, self).wread(filename)
449 return kwt.wread(filename, data)
447 return kwt.wread(filename, data)
450
448
451 def commit(self, files=None, text='', user=None, date=None,
449 def commit(self, files=None, text='', user=None, date=None,
452 match=None, force=False, force_editor=False,
450 match=None, force=False, force_editor=False,
453 p1=None, p2=None, extra={}, empty_ok=False):
451 p1=None, p2=None, extra={}, empty_ok=False):
454 wlock = lock = None
452 wlock = lock = None
455 _p1 = _p2 = None
453 _p1 = _p2 = None
456 try:
454 try:
457 wlock = self.wlock()
455 wlock = self.wlock()
458 lock = self.lock()
456 lock = self.lock()
459 # store and postpone commit hooks
457 # store and postpone commit hooks
460 commithooks = {}
458 commithooks = {}
461 for name, cmd in ui.configitems('hooks'):
459 for name, cmd in ui.configitems('hooks'):
462 if name.split('.', 1)[0] == 'commit':
460 if name.split('.', 1)[0] == 'commit':
463 commithooks[name] = cmd
461 commithooks[name] = cmd
464 ui.setconfig('hooks', name, None)
462 ui.setconfig('hooks', name, None)
465 if commithooks:
463 if commithooks:
466 # store parents for commit hook environment
464 # store parents for commit hook environment
467 if p1 is None:
465 if p1 is None:
468 _p1, _p2 = repo.dirstate.parents()
466 _p1, _p2 = repo.dirstate.parents()
469 else:
467 else:
470 _p1, _p2 = p1, p2 or nullid
468 _p1, _p2 = p1, p2 or nullid
471 _p1 = hex(_p1)
469 _p1 = hex(_p1)
472 if _p2 == nullid:
470 if _p2 == nullid:
473 _p2 = ''
471 _p2 = ''
474 else:
472 else:
475 _p2 = hex(_p2)
473 _p2 = hex(_p2)
476
474
477 n = super(kwrepo, self).commit(files, text, user, date, match,
475 n = super(kwrepo, self).commit(files, text, user, date, match,
478 force, force_editor, p1, p2,
476 force, force_editor, p1, p2,
479 extra, empty_ok)
477 extra, empty_ok)
480
478
481 # restore commit hooks
479 # restore commit hooks
482 for name, cmd in commithooks.iteritems():
480 for name, cmd in commithooks.iteritems():
483 ui.setconfig('hooks', name, cmd)
481 ui.setconfig('hooks', name, cmd)
484 if n is not None:
482 if n is not None:
485 kwt.overwrite(n, True, None)
483 kwt.overwrite(n, True, None)
486 repo.hook('commit', node=n, parent1=_p1, parent2=_p2)
484 repo.hook('commit', node=n, parent1=_p1, parent2=_p2)
487 return n
485 return n
488 finally:
486 finally:
489 del wlock, lock
487 del wlock, lock
490
488
491 # monkeypatches
489 # monkeypatches
492 def kwpatchfile_init(orig, self, ui, fname, missing=False):
490 def kwpatchfile_init(orig, self, ui, fname, missing=False):
493 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
491 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
494 rejects or conflicts due to expanded keywords in working dir.'''
492 rejects or conflicts due to expanded keywords in working dir.'''
495 orig(self, ui, fname, missing)
493 orig(self, ui, fname, missing)
496 # shrink keywords read from working dir
494 # shrink keywords read from working dir
497 self.lines = kwt.shrinklines(self.fname, self.lines)
495 self.lines = kwt.shrinklines(self.fname, self.lines)
498
496
499 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
497 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
500 opts=None):
498 opts=None):
501 '''Monkeypatch patch.diff to avoid expansion except when
499 '''Monkeypatch patch.diff to avoid expansion except when
502 comparing against working dir.'''
500 comparing against working dir.'''
503 if node2 is not None:
501 if node2 is not None:
504 kwt.matcher = util.never
502 kwt.matcher = util.never
505 elif node1 is not None and node1 != repo['.'].node():
503 elif node1 is not None and node1 != repo['.'].node():
506 kwt.restrict = True
504 kwt.restrict = True
507 return orig(repo, node1, node2, match, changes, opts)
505 return orig(repo, node1, node2, match, changes, opts)
508
506
509 def kwweb_skip(orig, web, req, tmpl):
507 def kwweb_skip(orig, web, req, tmpl):
510 '''Wraps webcommands.x turning off keyword expansion.'''
508 '''Wraps webcommands.x turning off keyword expansion.'''
511 kwt.matcher = util.never
509 kwt.matcher = util.never
512 return orig(web, req, tmpl)
510 return orig(web, req, tmpl)
513
511
514 repo.__class__ = kwrepo
512 repo.__class__ = kwrepo
515
513
516 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
514 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
517 extensions.wrapfunction(patch, 'diff', kw_diff)
515 extensions.wrapfunction(patch, 'diff', kw_diff)
518 for c in 'annotate changeset rev filediff diff'.split():
516 for c in 'annotate changeset rev filediff diff'.split():
519 extensions.wrapfunction(webcommands, c, kwweb_skip)
517 extensions.wrapfunction(webcommands, c, kwweb_skip)
520
518
521 cmdtable = {
519 cmdtable = {
522 'kwdemo':
520 'kwdemo':
523 (demo,
521 (demo,
524 [('d', 'default', None, _('show default keyword template maps')),
522 [('d', 'default', None, _('show default keyword template maps')),
525 ('f', 'rcfile', [], _('read maps from rcfile'))],
523 ('f', 'rcfile', [], _('read maps from rcfile'))],
526 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
524 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
527 'kwexpand': (expand, commands.walkopts,
525 'kwexpand': (expand, commands.walkopts,
528 _('hg kwexpand [OPTION]... [FILE]...')),
526 _('hg kwexpand [OPTION]... [FILE]...')),
529 'kwfiles':
527 'kwfiles':
530 (files,
528 (files,
531 [('a', 'all', None, _('show keyword status flags of all files')),
529 [('a', 'all', None, _('show keyword status flags of all files')),
532 ('i', 'ignore', None, _('show files excluded from expansion')),
530 ('i', 'ignore', None, _('show files excluded from expansion')),
533 ('u', 'untracked', None, _('additionally show untracked files')),
531 ('u', 'untracked', None, _('additionally show untracked files')),
534 ] + commands.walkopts,
532 ] + commands.walkopts,
535 _('hg kwfiles [OPTION]... [FILE]...')),
533 _('hg kwfiles [OPTION]... [FILE]...')),
536 'kwshrink': (shrink, commands.walkopts,
534 'kwshrink': (shrink, commands.walkopts,
537 _('hg kwshrink [OPTION]... [FILE]...')),
535 _('hg kwshrink [OPTION]... [FILE]...')),
538 }
536 }
General Comments 0
You need to be logged in to leave comments. Login now