35 # # or, if script in hgext folder: |
32 # # or, if script in hgext folder: |
36 # # hgext.keyword = |
33 # # hgext.keyword = |
37 |
34 |
38 '''keyword expansion in local repositories |
35 '''keyword expansion in local repositories |
39 |
36 |
40 This extension expands RCS/CVS-like or self-customized keywords in |
37 This extension expands RCS/CVS-like or self-customized $Keywords$ |
41 the text files selected by your configuration. |
38 in the text files selected by your configuration. |
42 |
39 |
43 Keywords are only expanded in local repositories and not logged by |
40 Keywords are only expanded in local repositories and not logged by |
44 Mercurial internally. The mechanism can be regarded as a convenience |
41 Mercurial internally. The mechanism can be regarded as a convenience |
45 for the current user and may be turned off anytime. |
42 for the current user or archive distribution. |
46 |
|
47 An additional date template filter {date|utcdate} is provided. |
|
48 |
|
49 Caveat: "hg import" might fail if the patches were exported from a |
|
50 repo with a different/no keyword setup, whereas "hg unbundle" is |
|
51 safe. |
|
52 |
43 |
53 Configuration is done in the [keyword] and [keywordmaps] sections of |
44 Configuration is done in the [keyword] and [keywordmaps] sections of |
54 hgrc files. |
45 hgrc files. |
55 |
46 |
56 Example: |
47 Example: |
57 [extensions] |
48 [extensions] |
58 hgext.keyword = |
49 hgext.keyword = |
59 |
50 |
60 [keyword] |
51 [keyword] |
61 # expand keywords in every python file, |
52 # expand keywords in every python file except those matching "x*" |
62 # except those matching "x*" |
|
63 **.py = |
53 **.py = |
64 x* = ignore |
54 x* = ignore |
65 |
55 |
66 For [keywordmaps] demonstration run "hg kwdemo". |
56 Note: the more specific you are in your [keyword] filename patterns |
|
57 the less you lose speed in huge repos. |
|
58 |
|
59 For a [keywordmaps] template mapping and expansion demonstration |
|
60 run "hg kwdemo". |
|
61 |
|
62 An additional date template filter {date|utcdate} is provided. |
|
63 |
|
64 You can replace the default template mappings with customized keywords |
|
65 and templates of your choice. |
|
66 Again, run "hg kwdemo" to control the results of your config changes. |
|
67 |
|
68 When you change keyword configuration, especially the active keywords, |
|
69 and do not want to store expanded keywords in change history, run |
|
70 "hg kwshrink", and then change configuration. |
|
71 |
|
72 Caveat: "hg import" fails if the patch context contains an active |
|
73 keyword. In that case run "hg kwshrink", reimport, and then |
|
74 "hg kwexpand". |
|
75 Or, better, use bundle/unbundle to share changes. |
67 ''' |
76 ''' |
68 |
77 |
|
78 from mercurial import commands, cmdutil, context, fancyopts |
|
79 from mercurial import filelog, localrepo, templater, util, hg |
69 from mercurial.i18n import gettext as _ |
80 from mercurial.i18n import gettext as _ |
70 from mercurial import commands, fancyopts, templater, util |
|
71 from mercurial import cmdutil, context, filelog, localrepo |
|
72 # findcmd might be in cmdutil or commands |
81 # findcmd might be in cmdutil or commands |
73 # depending on mercurial version |
82 # depending on mercurial version |
74 if hasattr(cmdutil, 'findcmd'): |
83 if hasattr(cmdutil, 'findcmd'): |
75 findcmd = cmdutil.findcmd |
84 findcmd = cmdutil.findcmd |
76 else: |
85 else: |
77 findcmd = commands.findcmd |
86 findcmd = commands.findcmd |
78 import os, re, shutil, sys, tempfile, time |
87 import os, re, shutil, sys, tempfile, time |
|
88 |
|
89 commands.optionalrepo += ' kwdemo' |
79 |
90 |
80 deftemplates = { |
91 deftemplates = { |
81 'Revision': '{node|short}', |
92 'Revision': '{node|short}', |
82 'Author': '{author|user}', |
93 'Author': '{author|user}', |
83 'Date': '{date|utcdate}', |
94 'Date': '{date|utcdate}', |
168 '''Returns text with all keyword substitutions removed.''' |
179 '''Returns text with all keyword substitutions removed.''' |
169 if util.binary(text): |
180 if util.binary(text): |
170 return text |
181 return text |
171 return self.re_kw.sub(r'$\1$', text) |
182 return self.re_kw.sub(r'$\1$', text) |
172 |
183 |
173 def overwrite(self, candidates, mn): |
184 def overwrite(self, candidates, manifest, expand=True, commit=True): |
174 '''Overwrites candidates in working dir expanding keywords.''' |
185 '''Overwrites candidates in working dir expanding keywords.''' |
|
186 if expand: |
|
187 sub = self.kwsub |
|
188 action = 'expanding' |
|
189 else: |
|
190 sub = r'$\1$' |
|
191 action = 'shrinking' |
|
192 if not commit: |
|
193 notify = self.ui.note |
|
194 else: |
|
195 notify = self.ui.debug |
175 files = [] |
196 files = [] |
176 m = self.repo.manifest.read(mn) |
|
177 for f in candidates: |
197 for f in candidates: |
178 data = self.repo.wread(f) |
198 data = self.repo.wread(f) |
179 if not util.binary(data): |
199 if not util.binary(data): |
180 self.path = f |
200 self.path = f |
181 data, kwct = self.re_kw.subn(self.kwsub, data) |
201 data, kwct = self.re_kw.subn(sub, data) |
182 if kwct: |
202 if kwct: |
183 self.ui.debug(_('overwriting %s expanding keywords\n') % f) |
203 notify(_('overwriting %s %s keywords\n') % (f, action)) |
184 self.repo.wwrite(f, data, m.flags(f)) |
204 self.repo.wwrite(f, data, manifest.flags(f)) |
185 files.append(f) |
205 files.append(f) |
186 if files: |
206 if files: |
187 self.repo.dirstate.update(files, 'n') |
207 self.repo.dirstate.update(files, 'n') |
188 |
208 |
189 class kwfilelog(filelog.filelog): |
209 class kwfilelog(filelog.filelog): |
211 if self.renamed(node): |
231 if self.renamed(node): |
212 t2 = super(kwfilelog, self).read(node) |
232 t2 = super(kwfilelog, self).read(node) |
213 return t2 != text |
233 return t2 != text |
214 return super(kwfilelog, self).cmp(node, text) |
234 return super(kwfilelog, self).cmp(node, text) |
215 |
235 |
|
236 def overwrite(ui, repo, files=None, expand=False): |
|
237 '''Expands/shrinks keywords in working directory.''' |
|
238 wlock = repo.wlock() |
|
239 try: |
|
240 ctx = repo.changectx() |
|
241 if not ctx: |
|
242 raise hg.RepoError(_('no changeset found')) |
|
243 for changed in repo.status()[:4]: |
|
244 if changed: |
|
245 raise util.Abort(_('local changes detected')) |
|
246 kwfmatcher = keywordmatcher(ui, repo) |
|
247 if kwfmatcher is None: |
|
248 ui.warn(_('no files configured for keyword expansion\n')) |
|
249 return |
|
250 m = ctx.manifest() |
|
251 if files: |
|
252 files = [f for f in files if f in m.keys()] |
|
253 else: |
|
254 files = m.keys() |
|
255 files = [f for f in files if kwfmatcher(f) and not os.path.islink(f)] |
|
256 if not files: |
|
257 ui.warn(_('given files not tracked or ' |
|
258 'not configured for expansion\n')) |
|
259 return |
|
260 kwt = kwtemplater(ui, repo, node=ctx.node()) |
|
261 kwt.overwrite(files, m, expand=expand, commit=False) |
|
262 finally: |
|
263 wlock.release() |
|
264 |
|
265 |
|
266 def shrink(ui, repo, *args): |
|
267 '''revert expanded keywords in working directory |
|
268 |
|
269 run before: |
|
270 disabling keyword expansion |
|
271 changing keyword expansion configuration |
|
272 or if you experience problems with "hg import" |
|
273 ''' |
|
274 overwrite(ui, repo, args, expand=False) |
|
275 |
|
276 def expand(ui, repo, *args): |
|
277 '''expand keywords in working directory |
|
278 |
|
279 run after (re)enabling keyword expansion |
|
280 ''' |
|
281 overwrite(ui, repo, args, expand=True) |
216 |
282 |
217 def demo(ui, repo, **opts): |
283 def demo(ui, repo, **opts): |
218 '''print [keywordmaps] configuration and an expansion example |
284 '''print [keywordmaps] configuration and an expansion example |
|
285 |
|
286 Show current or default keyword template maps and their expansion |
219 ''' |
287 ''' |
220 msg = 'hg keyword config and expansion example' |
288 msg = 'hg keyword config and expansion example' |
221 fn = 'demo.txt' |
289 fn = 'demo.txt' |
222 tmpdir = tempfile.mkdtemp('', 'kwdemo.') |
290 tmpdir = tempfile.mkdtemp('', 'kwdemo.') |
223 ui.note(_('creating temporary repo at %s\n') % tmpdir) |
291 ui.note(_('creating temporary repo at %s\n') % tmpdir) |