69 the risk of inadvertedly storing expanded keywords in the change history. |
69 the risk of inadvertedly storing expanded keywords in the change history. |
70 |
70 |
71 To force expansion after enabling it, or a configuration change, run |
71 To force expansion after enabling it, or a configuration change, run |
72 "hg kwexpand". |
72 "hg kwexpand". |
73 |
73 |
|
74 Also, when committing with the record extension or using mq's qrecord, be aware |
|
75 that keywords cannot be updated. Again, run "hg kwexpand" on the files in |
|
76 question to update keyword expansions after all changes have been checked in. |
|
77 |
74 Expansions spanning more than one line and incremental expansions, |
78 Expansions spanning more than one line and incremental expansions, |
75 like CVS' $Log$, are not supported. A keyword template map |
79 like CVS' $Log$, are not supported. A keyword template map |
76 "Log = {desc}" expands to the first line of the changeset description. |
80 "Log = {desc}" expands to the first line of the changeset description. |
77 |
81 |
78 Caveat: "hg import" fails if the patch context contains an active |
82 Caveat: "hg import" fails if the patch context contains an active |
79 keyword. In that case run "hg kwshrink", and then reimport. |
83 keyword. In that case run "hg kwshrink", and then reimport. |
80 Or, better, use bundle/unbundle to share changes. |
84 Or, better, use bundle/unbundle to share changes. |
81 ''' |
85 ''' |
82 |
86 |
83 from mercurial import commands, cmdutil, context |
87 from mercurial import commands, cmdutil, context, fancyopts |
84 from mercurial import filelog, localrepo, revlog, templater, util |
88 from mercurial import filelog, localrepo, revlog, templater, util |
85 from mercurial.node import * |
89 from mercurial.node import * |
86 from mercurial.i18n import gettext as _ |
90 from mercurial.i18n import gettext as _ |
87 import getopt, os.path, re, shutil, sys, tempfile, time |
91 import getopt, os.path, re, shutil, sys, tempfile, time |
88 |
92 |
89 # backwards compatibility hacks |
93 # backwards compatibility hacks |
|
94 |
|
95 try: |
|
96 # cmdutil.parse moves to dispatch._parse in 18a9fbb5cd78 |
|
97 # also avoid name conflict with other dispatch package(s) |
|
98 from mercurial.dispatch import _parse |
|
99 except ImportError: |
|
100 try: |
|
101 # commands.parse moves to cmdutil.parse in 0c61124ad877 |
|
102 _parse = cmdutil.parse |
|
103 except AttributeError: |
|
104 _parse = commands.parse |
90 |
105 |
91 def _wwrite(repo, f, data, mf): |
106 def _wwrite(repo, f, data, mf): |
92 '''Makes repo.wwrite backwards compatible.''' |
107 '''Makes repo.wwrite backwards compatible.''' |
93 # 656e06eebda7 removed file descriptor argument |
108 # 656e06eebda7 removed file descriptor argument |
94 # 67982d3ee76c added flags argument |
109 # 67982d3ee76c added flags argument |
167 state[name] = True |
182 state[name] = True |
168 |
183 |
169 # return unparsed args |
184 # return unparsed args |
170 return args |
185 return args |
171 |
186 |
172 def findcmd(ui, cmd, table): |
187 fancyopts.fancyopts = _fancyopts |
173 '''findcmd has table argument since 18a9fbb5cd78.''' |
|
174 try: |
|
175 return findcmd.findcmd(ui, cmd, table) |
|
176 except TypeError: |
|
177 return findcmd.findcmd(ui, cmd) |
|
178 # findcmd in commands until 0c61124ad877 |
|
179 try: |
|
180 findcmd.findcmd = cmdutil.findcmd |
|
181 findcmd.__doc__ = cmdutil.findcmd.__doc__ |
|
182 except AttributeError: |
|
183 findcmd.findcmd = commands.findcmd |
|
184 findcmd.__doc__ = commands.findcmd.__doc__ |
|
185 |
188 |
186 |
189 |
187 commands.optionalrepo += ' kwdemo' |
190 commands.optionalrepo += ' kwdemo' |
188 |
191 |
189 def utcdate(date): |
192 def utcdate(date): |
190 '''Returns hgdate in cvs-like UTC format.''' |
193 '''Returns hgdate in cvs-like UTC format.''' |
191 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0])) |
194 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0])) |
|
195 |
|
196 def _kwrestrict(cmd): |
|
197 '''Returns True if cmd should trigger restricted expansion. |
|
198 Keywords will only expanded when writing to working dir. |
|
199 Crucial for mq as expanded keywords should not make it into patches.''' |
|
200 return cmd in ('diff1', |
|
201 'qimport', 'qnew', 'qpush', 'qrefresh', 'record', 'qrecord') |
|
202 |
192 |
203 |
193 _kwtemplater = None |
204 _kwtemplater = None |
194 |
205 |
195 class kwtemplater(object): |
206 class kwtemplater(object): |
196 ''' |
207 ''' |
205 'Source': '{root}/{file},v', |
216 'Source': '{root}/{file},v', |
206 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}', |
217 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}', |
207 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}', |
218 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}', |
208 } |
219 } |
209 |
220 |
210 def __init__(self, ui, repo, inc, exc): |
221 def __init__(self, ui, repo, inc, exc, hgcmd): |
211 self.ui = ui |
222 self.ui = ui |
212 self.repo = repo |
223 self.repo = repo |
213 self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1] |
224 self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1] |
|
225 self.hgcmd = hgcmd |
214 self.commitnode = None |
226 self.commitnode = None |
215 self.path = '' |
227 self.path = '' |
216 |
228 |
217 kwmaps = self.ui.configitems('keywordmaps') |
229 kwmaps = self.ui.configitems('keywordmaps') |
218 if kwmaps: # override default templates |
230 if kwmaps: # override default templates |
255 |
267 |
256 return subfunc(kwsub, data) |
268 return subfunc(kwsub, data) |
257 |
269 |
258 def expand(self, node, data): |
270 def expand(self, node, data): |
259 '''Returns data with keywords expanded.''' |
271 '''Returns data with keywords expanded.''' |
260 if util.binary(data): |
272 if util.binary(data) or _kwrestrict(self.hgcmd): |
261 return data |
273 return data |
262 return self.substitute(node, data, self.re_kw.sub) |
274 return self.substitute(node, data, self.re_kw.sub) |
263 |
275 |
264 def process(self, node, data, expand): |
276 def process(self, node, data, expand): |
265 '''Returns a tuple: data, count. |
277 '''Returns a tuple: data, count. |
495 Wraps commit to overwrite configured files with updated |
507 Wraps commit to overwrite configured files with updated |
496 keyword substitutions. |
508 keyword substitutions. |
497 This is done for local repos only, and only if there are |
509 This is done for local repos only, and only if there are |
498 files configured at all for keyword substitution.''' |
510 files configured at all for keyword substitution.''' |
499 |
511 |
500 def kwbailout(): |
512 if not repo.local(): |
501 '''Obtains command via simplified cmdline parsing, |
|
502 returns True if keyword expansion not needed.''' |
|
503 nokwcommands = ('add', 'addremove', 'bundle', 'clone', 'copy', |
|
504 'export', 'grep', 'identify', 'incoming', 'init', |
|
505 'log', 'outgoing', 'push', 'remove', 'rename', |
|
506 'rollback', 'tip', |
|
507 'convert') |
|
508 args = fancyopts(sys.argv[1:], commands.globalopts, {}) |
|
509 if args: |
|
510 aliases, i = findcmd(ui, args[0], commands.table) |
|
511 return aliases[0] in nokwcommands |
|
512 |
|
513 if not repo.local() or kwbailout(): |
|
514 return |
513 return |
|
514 |
|
515 nokwcommands = ('add', 'addremove', 'bundle', 'clone', 'copy', |
|
516 'export', 'grep', 'identify', 'incoming', 'init', |
|
517 'log', 'outgoing', 'push', 'remove', 'rename', |
|
518 'rollback', 'tip', |
|
519 'convert') |
|
520 hgcmd, func, args, opts, cmdopts = _parse(ui, sys.argv[1:]) |
|
521 if hgcmd in nokwcommands: |
|
522 return |
|
523 |
|
524 if hgcmd == 'diff': |
|
525 # only expand if comparing against working dir |
|
526 node1, node2 = cmdutil.revpair(repo, cmdopts.get('rev')) |
|
527 if node2 is not None: |
|
528 return |
|
529 # shrink if rev is not current node |
|
530 if node1 is not None and node1 != repo.changectx().node(): |
|
531 hgcmd = 'diff1' |
515 |
532 |
516 inc, exc = [], ['.hgtags'] |
533 inc, exc = [], ['.hgtags'] |
517 for pat, opt in ui.configitems('keyword'): |
534 for pat, opt in ui.configitems('keyword'): |
518 if opt != 'ignore': |
535 if opt != 'ignore': |
519 inc.append(pat) |
536 inc.append(pat) |
521 exc.append(pat) |
538 exc.append(pat) |
522 if not inc: |
539 if not inc: |
523 return |
540 return |
524 |
541 |
525 global _kwtemplater |
542 global _kwtemplater |
526 _kwtemplater = kwtemplater(ui, repo, inc, exc) |
543 _kwtemplater = kwtemplater(ui, repo, inc, exc, hgcmd) |
527 |
544 |
528 class kwrepo(repo.__class__): |
545 class kwrepo(repo.__class__): |
529 def file(self, f, kwmatch=False): |
546 def file(self, f, kwmatch=False): |
530 if f[0] == '/': |
547 if f[0] == '/': |
531 f = f[1:] |
548 f = f[1:] |
532 if kwmatch or _kwtemplater.matcher(f): |
549 if kwmatch or _kwtemplater.matcher(f): |
533 return kwfilelog(self.sopener, f) |
550 return kwfilelog(self.sopener, f) |
534 return filelog.filelog(self.sopener, f) |
551 return filelog.filelog(self.sopener, f) |
|
552 |
|
553 def wread(self, filename): |
|
554 data = super(kwrepo, self).wread(filename) |
|
555 if _kwrestrict(hgcmd) and _kwtemplater.matcher(filename): |
|
556 return _kwtemplater.shrink(data) |
|
557 return data |
535 |
558 |
536 def _commit(self, files, text, user, date, match, force, lock, wlock, |
559 def _commit(self, files, text, user, date, match, force, lock, wlock, |
537 force_editor, p1, p2, extra): |
560 force_editor, p1, p2, extra): |
538 '''Private commit wrapper for backwards compatibility.''' |
561 '''Private commit wrapper for backwards compatibility.''' |
539 try: |
562 try: |