77 |
77 |
78 Expansions spanning more than one line and incremental expansions, |
78 Expansions spanning more than one line and incremental expansions, |
79 like CVS' $Log$, are not supported. A keyword template map |
79 like CVS' $Log$, are not supported. A keyword template map |
80 "Log = {desc}" expands to the first line of the changeset description. |
80 "Log = {desc}" expands to the first line of the changeset description. |
81 |
81 |
82 Caveat: "hg import" fails if the patch context contains an active |
82 Caveat: With Mercurial versions prior to 4574925db5c0 "hg import" might |
83 keyword. In that case run "hg kwshrink", and then reimport. |
83 cause rejects if the patch context contains an active keyword. |
|
84 In that case run "hg kwshrink", and then reimport. |
84 Or, better, use bundle/unbundle to share changes. |
85 Or, better, use bundle/unbundle to share changes. |
85 ''' |
86 ''' |
86 |
87 |
87 from mercurial import commands, cmdutil, context, fancyopts |
88 from mercurial import commands, cmdutil, context, fancyopts |
88 from mercurial import filelog, localrepo, revlog, templater, util |
89 from mercurial import filelog, localrepo, revlog, templater, util |
89 from mercurial.node import * |
90 from mercurial.node import * |
90 from mercurial.i18n import gettext as _ |
91 from mercurial.i18n import gettext as _ |
91 import getopt, os.path, re, shutil, sys, tempfile, time |
92 import getopt, os.path, re, shutil, sys, tempfile, time |
92 |
93 |
|
94 commands.optionalrepo += ' kwdemo' |
|
95 |
|
96 # hg commands that do not act on keywords |
|
97 nokwcommands = ('add addremove bundle copy export grep identify incoming init' |
|
98 ' log outgoing push remove rename rollback tip convert') |
|
99 |
|
100 # hg commands that trigger expansion only when writing to working dir, |
|
101 # not when reading filelog, and unexpand when reading from working dir |
|
102 restricted = 'diff1 record qfold qimport qnew qpush qrefresh qrecord' |
|
103 |
|
104 def utcdate(date): |
|
105 '''Returns hgdate in cvs-like UTC format.''' |
|
106 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0])) |
|
107 |
|
108 |
|
109 _kwtemplater = None |
|
110 |
93 # backwards compatibility hacks |
111 # backwards compatibility hacks |
|
112 |
|
113 try: |
|
114 # avoid spurious rejects if patchfile is available |
|
115 from mercurial.patch import patchfile |
|
116 _patchfile_init = patchfile.__init__ |
|
117 |
|
118 def _kwpatchfile_init(self, ui, fname, missing=False): |
|
119 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid |
|
120 rejects or conflicts due to expanded keywords in working dir.''' |
|
121 _patchfile_init(self, ui, fname, missing=missing) |
|
122 if _kwtemplater.matcher(self.fname): |
|
123 # shrink keywords read from working dir |
|
124 kwshrunk = _kwtemplater.shrink(''.join(self.lines)) |
|
125 self.lines = kwshrunk.splitlines(True) |
|
126 except ImportError: |
|
127 pass |
|
128 |
|
129 try: |
|
130 # templatefilters module introduced in 9f1e6ab76069 |
|
131 from mercurial import templatefilters |
|
132 template_filters = templatefilters.filters |
|
133 template_firstline = templatefilters.firstline |
|
134 except ImportError: |
|
135 template_filters = templater.common_filters |
|
136 template_firstline = templater.firstline |
94 |
137 |
95 try: |
138 try: |
96 # cmdutil.parse moves to dispatch._parse in 18a9fbb5cd78 |
139 # cmdutil.parse moves to dispatch._parse in 18a9fbb5cd78 |
97 # also avoid name conflict with other dispatch package(s) |
140 # also avoid name conflict with other dispatch package(s) |
98 from mercurial.dispatch import _parse |
141 from mercurial.dispatch import _parse |
185 return args |
228 return args |
186 |
229 |
187 fancyopts.fancyopts = _fancyopts |
230 fancyopts.fancyopts = _fancyopts |
188 |
231 |
189 |
232 |
190 commands.optionalrepo += ' kwdemo' |
|
191 |
|
192 # hg commands that trigger expansion only when writing to working dir, |
|
193 # not when reading filelog, and unexpand when reading from working dir |
|
194 restricted = ('diff1', 'record', |
|
195 'qfold', 'qimport', 'qnew', 'qpush', 'qrefresh', 'qrecord') |
|
196 |
|
197 def utcdate(date): |
|
198 '''Returns hgdate in cvs-like UTC format.''' |
|
199 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0])) |
|
200 |
|
201 |
|
202 _kwtemplater = None |
|
203 |
|
204 class kwtemplater(object): |
233 class kwtemplater(object): |
205 ''' |
234 ''' |
206 Sets up keyword templates, corresponding keyword regex, and |
235 Sets up keyword templates, corresponding keyword regex, and |
207 provides keyword substitution functions. |
236 provides keyword substitution functions. |
208 ''' |
237 ''' |
214 'Source': '{root}/{file},v', |
243 'Source': '{root}/{file},v', |
215 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}', |
244 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}', |
216 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}', |
245 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}', |
217 } |
246 } |
218 |
247 |
219 def __init__(self, ui, repo, inc, exc, hgcmd): |
248 def __init__(self, ui, repo, inc, exc, restricted): |
220 self.ui = ui |
249 self.ui = ui |
221 self.repo = repo |
250 self.repo = repo |
222 self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1] |
251 self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1] |
223 self.hgcmd = hgcmd |
252 self.restricted = restricted |
224 self.commitnode = None |
253 self.commitnode = None |
225 self.path = '' |
254 self.path = '' |
226 |
255 |
227 kwmaps = self.ui.configitems('keywordmaps') |
256 kwmaps = self.ui.configitems('keywordmaps') |
228 if kwmaps: # override default templates |
257 if kwmaps: # override default templates |
259 '''Substitutes keyword using corresponding template.''' |
288 '''Substitutes keyword using corresponding template.''' |
260 kw = mobj.group(1) |
289 kw = mobj.group(1) |
261 self.ct.use_template(self.templates[kw]) |
290 self.ct.use_template(self.templates[kw]) |
262 self.ui.pushbuffer() |
291 self.ui.pushbuffer() |
263 self.ct.show(changenode=fnode, root=self.repo.root, file=self.path) |
292 self.ct.show(changenode=fnode, root=self.repo.root, file=self.path) |
264 return '$%s: %s $' % (kw, templater.firstline(self.ui.popbuffer())) |
293 return '$%s: %s $' % (kw, template_firstline(self.ui.popbuffer())) |
265 |
294 |
266 return subfunc(kwsub, data) |
295 return subfunc(kwsub, data) |
267 |
296 |
268 def expand(self, node, data): |
297 def expand(self, node, data): |
269 '''Returns data with keywords expanded.''' |
298 '''Returns data with keywords expanded.''' |
270 if util.binary(data) or self.hgcmd in restricted: |
299 if self.restricted or util.binary(data): |
271 return data |
300 return data |
272 return self.substitute(node, data, self.re_kw.sub) |
301 return self.substitute(node, data, self.re_kw.sub) |
273 |
302 |
274 def process(self, node, data, expand): |
303 def process(self, node, data, expand): |
275 '''Returns a tuple: data, count. |
304 '''Returns a tuple: data, count. |
508 files configured at all for keyword substitution.''' |
537 files configured at all for keyword substitution.''' |
509 |
538 |
510 if not repo.local(): |
539 if not repo.local(): |
511 return |
540 return |
512 |
541 |
513 nokwcommands = ('add', 'addremove', 'bundle', 'clone', 'copy', |
|
514 'export', 'grep', 'identify', 'incoming', 'init', |
|
515 'log', 'outgoing', 'push', 'remove', 'rename', |
|
516 'rollback', 'tip', |
|
517 'convert') |
|
518 hgcmd, func, args, opts, cmdopts = _parse(ui, sys.argv[1:]) |
542 hgcmd, func, args, opts, cmdopts = _parse(ui, sys.argv[1:]) |
519 if hgcmd in nokwcommands: |
543 if hgcmd in nokwcommands.split(): |
520 return |
544 return |
521 |
545 |
522 if hgcmd == 'diff': |
546 if hgcmd == 'diff': |
523 # only expand if comparing against working dir |
547 # only expand if comparing against working dir |
524 node1, node2 = cmdutil.revpair(repo, cmdopts.get('rev')) |
548 node1, node2 = cmdutil.revpair(repo, cmdopts.get('rev')) |
548 return kwfilelog(self.sopener, f) |
573 return kwfilelog(self.sopener, f) |
549 return filelog.filelog(self.sopener, f) |
574 return filelog.filelog(self.sopener, f) |
550 |
575 |
551 def wread(self, filename): |
576 def wread(self, filename): |
552 data = super(kwrepo, self).wread(filename) |
577 data = super(kwrepo, self).wread(filename) |
553 if hgcmd in restricted and _kwtemplater.matcher(filename): |
578 if _restricted and _kwtemplater.matcher(filename): |
554 return _kwtemplater.shrink(data) |
579 return _kwtemplater.shrink(data) |
555 return data |
580 return data |
556 |
581 |
557 def _commit(self, files, text, user, date, match, force, lock, wlock, |
582 def _commit(self, files, text, user, date, match, force, lock, wlock, |
558 force_editor, p1, p2, extra): |
583 force_editor, p1, p2, extra, empty_ok): |
559 '''Private commit wrapper for backwards compatibility.''' |
584 '''Private commit wrapper for backwards compatibility.''' |
560 try: |
585 try: |
561 return super(kwrepo, self).commit(files=files, text=text, |
586 return super(kwrepo, self).commit(files=files, text=text, |
562 user=user, date=date, |
587 user=user, date=date, |
563 match=match, force=force, |
588 match=match, force=force, |
564 lock=lock, wlock=wlock, |
589 lock=lock, wlock=wlock, |
565 force_editor=force_editor, |
590 force_editor=force_editor, |
566 p1=p1, p2=p2, extra=extra) |
591 p1=p1, p2=p2, extra=extra) |
567 except TypeError: |
592 except TypeError: |
568 return super(kwrepo, self).commit(files=files, text=text, |
593 try: |
569 user=user, date=date, |
594 return super(kwrepo, self).commit(files=files, text=text, |
570 match=match, force=force, |
595 user=user, date=date, |
571 force_editor=force_editor, |
596 match=match, force=force, |
572 p1=p1, p2=p2, extra=extra) |
597 force_editor=force_editor, |
|
598 p1=p1, p2=p2, |
|
599 extra=extra, |
|
600 empty_ok=empty_ok) |
|
601 except TypeError: |
|
602 return super(kwrepo, self).commit(files=files, text=text, |
|
603 user=user, date=date, |
|
604 match=match, force=force, |
|
605 force_editor=force_editor, |
|
606 p1=p1, p2=p2, extra=extra) |
573 |
607 |
574 def commit(self, files=None, text='', user=None, date=None, |
608 def commit(self, files=None, text='', user=None, date=None, |
575 match=util.always, force=False, lock=None, wlock=None, |
609 match=util.always, force=False, lock=None, wlock=None, |
576 force_editor=False, p1=None, p2=None, extra={}): |
610 force_editor=False, p1=None, p2=None, extra={}, |
|
611 empty_ok=False): |
577 # (w)lock arguments removed in 126f527b3ba3 |
612 # (w)lock arguments removed in 126f527b3ba3 |
578 # so they are None or what was passed to commit |
613 # so they are None or what was passed to commit |
579 # use private _(w)lock for deletion |
614 # use private _(w)lock for deletion |
580 _lock = lock |
615 _lock = lock |
581 _wlock = wlock |
616 _wlock = wlock |
603 _p2 = '' |
638 _p2 = '' |
604 else: |
639 else: |
605 _p2 = hex(_p2) |
640 _p2 = hex(_p2) |
606 |
641 |
607 node = self._commit(files, text, user, date, match, force, |
642 node = self._commit(files, text, user, date, match, force, |
608 _lock, _wlock, force_editor, p1, p2, extra) |
643 _lock, _wlock, force_editor, p1, p2, extra, |
|
644 empty_ok) |
609 |
645 |
610 # restore commit hooks |
646 # restore commit hooks |
611 for name, cmd in commithooks.iteritems(): |
647 for name, cmd in commithooks.iteritems(): |
612 ui.setconfig('hooks', name, cmd) |
648 ui.setconfig('hooks', name, cmd) |
613 if node is not None: |
649 if node is not None: |