104 |
104 |
105 def utcdate(date): |
105 def utcdate(date): |
106 '''Returns hgdate in cvs-like UTC format.''' |
106 '''Returns hgdate in cvs-like UTC format.''' |
107 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0])) |
107 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0])) |
108 |
108 |
|
109 def textsafe(s): |
|
110 '''Safe version of util.binary with reversed logic. |
|
111 Note: argument may not be None, which is allowed for util.binary.''' |
|
112 return '\0' not in s |
109 |
113 |
110 # make keyword tools accessible |
114 # make keyword tools accessible |
111 kwtools = {'templater': None, 'hgcmd': None} |
115 kwtools = {'templater': None, 'hgcmd': None} |
112 |
116 |
113 # monkeypatches and backwards compatibility hacks |
117 # monkeypatch argument parsing |
114 |
118 # due to backwards compatibility this can't be done in uisetup |
115 try: |
119 # uisetup introduced with extensions module in 930ed513c864 |
116 # cmdutil.parse moves to dispatch._parse in 18a9fbb5cd78 |
|
117 from mercurial import dispatch |
|
118 _dispatch_parse = dispatch._parse |
|
119 except ImportError: |
|
120 try: |
|
121 # commands.parse moves to cmdutil.parse in 0c61124ad877 |
|
122 _dispatch_parse = cmdutil.parse |
|
123 except AttributeError: |
|
124 _dispatch_parse = commands.parse |
|
125 |
|
126 def _kwdispatch_parse(ui, args): |
120 def _kwdispatch_parse(ui, args): |
127 '''Monkeypatch dispatch._parse to obtain running hg command.''' |
121 '''Monkeypatch dispatch._parse to obtain running hg command.''' |
128 cmd, func, args, options, cmdoptions = _dispatch_parse(ui, args) |
122 cmd, func, args, options, cmdoptions = _dispatch_parse(ui, args) |
129 kwtools['hgcmd'] = cmd |
123 kwtools['hgcmd'] = cmd |
130 return cmd, func, args, options, cmdoptions |
124 return cmd, func, args, options, cmdoptions |
131 |
125 |
132 try: |
126 try: |
133 setattr(dispatch, '_parse', _kwdispatch_parse) |
127 # cmdutil.parse moves to dispatch._parse in 18a9fbb5cd78 |
134 except (NameError, ImportError): |
128 from mercurial import dispatch |
135 # 0.9.4 needs ImportError |
129 _dispatch_parse = dispatch._parse |
136 if hasattr(cmdutil, 'parse'): |
130 dispatch._parse = _kwdispatch_parse |
|
131 except ImportError: |
|
132 try: |
|
133 # commands.parse moves to cmdutil.parse in 0c61124ad877 |
|
134 _dispatch_parse = cmdutil.parse |
137 cmdutil.parse = _kwdispatch_parse |
135 cmdutil.parse = _kwdispatch_parse |
138 else: |
136 except AttributeError: |
|
137 _dispatch_parse = commands.parse |
139 commands.parse = _kwdispatch_parse |
138 commands.parse = _kwdispatch_parse |
140 |
|
141 try: |
|
142 # avoid spurious rejects if patchfile is available |
|
143 _patchfile_init = patch.patchfile.__init__ |
|
144 |
|
145 def _kwpatchfile_init(self, ui, fname, missing=False): |
|
146 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid |
|
147 rejects or conflicts due to expanded keywords in working dir.''' |
|
148 try: |
|
149 _patchfile_init(self, ui, fname, missing=missing) |
|
150 except TypeError: |
|
151 # "missing" arg added in e90e72c6b4c7 |
|
152 _patchfile_init(self, ui, fname) |
|
153 self.lines = kwtools['templater'].shrinklines(self.fname, self.lines) |
|
154 except AttributeError: |
|
155 pass |
|
156 |
|
157 _patch_diff = patch.diff |
|
158 def _kw_diff(repo, node1=None, node2=None, files=None, match=util.always, |
|
159 fp=None, changes=None, opts=None): |
|
160 # only expand if comparing against working dir |
|
161 if node2 is not None: |
|
162 kwtools['templater'].matcher = util.never |
|
163 elif node1 is not None and node1 != repo.changectx().node(): |
|
164 kwtools['templater'].restrict = True |
|
165 _patch_diff(repo, node1=node1, node2=node2, files=files, match=match, |
|
166 fp=fp, changes=changes, opts=opts) |
|
167 |
|
168 try: |
|
169 from mercurial.hgweb import webcommands |
|
170 _webcommands_changeset = webcommands.changeset |
|
171 _webcommands_filediff = webcommands.filediff |
|
172 |
|
173 def _kwweb_changeset(web, req, tmpl): |
|
174 '''Wraps webcommands.changeset turning off keyword expansion.''' |
|
175 kwtools['templater'].matcher = util.never |
|
176 return _webcommands_changeset(web, req, tmpl) |
|
177 |
|
178 def _kwweb_filediff(web, req, tmpl): |
|
179 '''Wraps webcommands.filediff turning off keyword expansion.''' |
|
180 kwtools['templater'].matcher = util.never |
|
181 return _webcommands_filediff(web, req, tmpl) |
|
182 |
|
183 webcommands.changeset = webcommands.rev = _kwweb_changeset |
|
184 webcommands.filediff = webcommands.diff = _kwweb_filediff |
|
185 |
|
186 except ImportError: |
|
187 from mercurial.hgweb.hgweb_mod import hgweb |
|
188 |
|
189 def _kwweb_do_changeset(self, req): |
|
190 kwtools['templater'].matcher = util.never |
|
191 req.write(self.changeset(self.changectx(req))) |
|
192 |
|
193 def _kwweb_do_filediff(self, req): |
|
194 kwtools['templater'].matcher = util.never |
|
195 req.write(self.filediff(self.filectx(req))) |
|
196 |
|
197 hgweb.do_changeset = hgweb.do_rev = _kwweb_do_changeset |
|
198 hgweb.do_filediff = hgweb.do_diff = _kwweb_do_filediff |
|
199 |
139 |
200 try: |
140 try: |
201 # templatefilters module introduced in 9f1e6ab76069 |
141 # templatefilters module introduced in 9f1e6ab76069 |
202 from mercurial import templatefilters |
142 from mercurial import templatefilters |
203 template_filters = templatefilters.filters |
143 template_filters = templatefilters.filters |
311 self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1] |
251 self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1] |
312 self.restrict = kwtools['hgcmd'] in restricted.split() |
252 self.restrict = kwtools['hgcmd'] in restricted.split() |
313 |
253 |
314 kwmaps = self.ui.configitems('keywordmaps') |
254 kwmaps = self.ui.configitems('keywordmaps') |
315 if kwmaps: # override default templates |
255 if kwmaps: # override default templates |
316 kwmaps = [(k, templater.parsestring(v, quoted=False)) |
256 kwmaps = [(k, templater.parsestring(v, False)) |
317 for (k, v) in kwmaps] |
257 for (k, v) in kwmaps] |
318 self.templates = dict(kwmaps) |
258 self.templates = dict(kwmaps) |
319 escaped = map(re.escape, self.templates.keys()) |
259 escaped = map(re.escape, self.templates.keys()) |
320 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped) |
260 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped) |
321 self.re_kw = re.compile(kwpat) |
261 self.re_kw = re.compile(kwpat) |
349 return '$%s: %s $' % (kw, template_firstline(self.ui.popbuffer())) |
289 return '$%s: %s $' % (kw, template_firstline(self.ui.popbuffer())) |
350 return subfunc(kwsub, data) |
290 return subfunc(kwsub, data) |
351 |
291 |
352 def expand(self, path, node, data): |
292 def expand(self, path, node, data): |
353 '''Returns data with keywords expanded.''' |
293 '''Returns data with keywords expanded.''' |
354 if not self.restrict and self.matcher(path) and not util.binary(data): |
294 if not self.restrict and self.matcher(path) and textsafe(data): |
355 changenode = self.getnode(path, node) |
295 changenode = self.getnode(path, node) |
356 return self.substitute(data, path, changenode, self.re_kw.sub) |
296 return self.substitute(data, path, changenode, self.re_kw.sub) |
357 return data |
297 return data |
358 |
298 |
359 def iskwfile(self, path, islink): |
299 def iskwfile(self, path, islink): |
360 '''Returns true if path matches [keyword] pattern |
300 '''Returns true if path matches [keyword] pattern |
361 and is not a symbolic link. |
301 and is not a symbolic link. |
362 Caveat: localrepository._link fails on Windows.''' |
302 Caveat: localrepository._link fails on Windows.''' |
363 return self.matcher(path) and not islink(path) |
303 return self.matcher(path) and not islink(path) |
364 |
304 |
365 def overwrite(self, node=None, expand=True, files=None): |
305 def overwrite(self, node, expand, files): |
366 '''Overwrites selected files expanding/shrinking keywords.''' |
306 '''Overwrites selected files expanding/shrinking keywords.''' |
367 ctx = self.repo.changectx(node) |
307 ctx = self.repo.changectx(node) |
368 mf = ctx.manifest() |
308 mf = ctx.manifest() |
369 if node is not None: # commit |
309 if node is not None: # commit |
370 files = [f for f in ctx.files() if f in mf] |
310 files = [f for f in ctx.files() if f in mf] |
399 '''Unconditionally removes all keyword substitutions from text.''' |
339 '''Unconditionally removes all keyword substitutions from text.''' |
400 return self.re_kw.sub(r'$\1$', text) |
340 return self.re_kw.sub(r'$\1$', text) |
401 |
341 |
402 def shrink(self, fname, text): |
342 def shrink(self, fname, text): |
403 '''Returns text with all keyword substitutions removed.''' |
343 '''Returns text with all keyword substitutions removed.''' |
404 if self.matcher(fname) and not util.binary(text): |
344 if self.matcher(fname) and textsafe(text): |
405 return self.shrinktext(text) |
345 return self.shrinktext(text) |
406 return text |
346 return text |
407 |
347 |
408 def shrinklines(self, fname, lines): |
348 def shrinklines(self, fname, lines): |
409 '''Returns lines with keyword substitutions removed.''' |
349 '''Returns lines with keyword substitutions removed.''' |
410 if self.matcher(fname): |
350 if self.matcher(fname): |
411 text = ''.join(lines) |
351 text = ''.join(lines) |
412 if not util.binary(text): |
352 if textsafe(text): |
413 return self.shrinktext(text).splitlines(True) |
353 return self.shrinktext(text).splitlines(True) |
414 return lines |
354 return lines |
415 |
355 |
416 def wread(self, fname, data): |
356 def wread(self, fname, data): |
417 '''If in restricted mode returns data read from wdir with |
357 '''If in restricted mode returns data read from wdir with |
421 class kwfilelog(filelog.filelog): |
361 class kwfilelog(filelog.filelog): |
422 ''' |
362 ''' |
423 Subclass of filelog to hook into its read, add, cmp methods. |
363 Subclass of filelog to hook into its read, add, cmp methods. |
424 Keywords are "stored" unexpanded, and processed on reading. |
364 Keywords are "stored" unexpanded, and processed on reading. |
425 ''' |
365 ''' |
426 def __init__(self, opener, path): |
366 def __init__(self, opener, kwt, path): |
427 super(kwfilelog, self).__init__(opener, path) |
367 super(kwfilelog, self).__init__(opener, path) |
428 self.kwt = kwtools['templater'] |
368 self.kwt = kwt |
429 self.path = path |
369 self.path = path |
430 |
370 |
431 def read(self, node): |
371 def read(self, node): |
432 '''Expands keywords when reading filelog.''' |
372 '''Expands keywords when reading filelog.''' |
433 data = super(kwfilelog, self).read(node) |
373 data = super(kwfilelog, self).read(node) |
434 return self.kwt.expand(self.path, node, data) |
374 return self.kwt.expand(self.path, node, data) |
435 |
375 |
436 def add(self, text, meta, tr, link, p1=None, p2=None): |
376 def add(self, text, meta, tr, link, p1=None, p2=None): |
437 '''Removes keyword substitutions when adding to filelog.''' |
377 '''Removes keyword substitutions when adding to filelog.''' |
438 text = self.kwt.shrink(self.path, text) |
378 text = self.kwt.shrink(self.path, text) |
439 return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2) |
379 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2) |
440 |
380 |
441 def cmp(self, node, text): |
381 def cmp(self, node, text): |
442 '''Removes keyword substitutions for comparison.''' |
382 '''Removes keyword substitutions for comparison.''' |
443 text = self.kwt.shrink(self.path, text) |
383 text = self.kwt.shrink(self.path, text) |
444 if self.renamed(node): |
384 if self.renamed(node): |
634 |
574 |
635 class kwrepo(repo.__class__): |
575 class kwrepo(repo.__class__): |
636 def file(self, f): |
576 def file(self, f): |
637 if f[0] == '/': |
577 if f[0] == '/': |
638 f = f[1:] |
578 f = f[1:] |
639 return kwfilelog(self.sopener, f) |
579 return kwfilelog(self.sopener, kwt, f) |
640 |
580 |
641 def wread(self, filename): |
581 def wread(self, filename): |
642 data = super(kwrepo, self).wread(filename) |
582 data = super(kwrepo, self).wread(filename) |
643 return kwt.wread(filename, data) |
583 return kwt.wread(filename, data) |
644 |
584 |
645 def _commit(self, files, text, user, date, match, force, lock, wlock, |
585 def _commit(self, files, text, user, date, match, force, lock, wlock, |
646 force_editor, p1, p2, extra, empty_ok): |
586 force_editor, p1, p2, extra, empty_ok): |
647 '''Private commit wrapper for backwards compatibility.''' |
587 '''Private commit wrapper for backwards compatibility.''' |
648 try: |
588 try: |
649 return super(kwrepo, self).commit(files=files, text=text, |
589 return super(kwrepo, self).commit(files, text, user, date, |
650 user=user, date=date, |
590 match, force, |
651 match=match, force=force, |
|
652 lock=lock, wlock=wlock, |
591 lock=lock, wlock=wlock, |
653 force_editor=force_editor, |
592 force_editor=force_editor, |
654 p1=p1, p2=p2, extra=extra) |
593 p1=p1, p2=p2, extra=extra) |
655 except TypeError: |
594 except TypeError: |
656 try: |
595 try: |
657 return super(kwrepo, self).commit(files=files, text=text, |
596 return super(kwrepo, |
658 user=user, date=date, |
597 self).commit(files, text, user, date, |
659 match=match, force=force, |
598 match, force, |
660 force_editor=force_editor, |
599 force_editor=force_editor, |
661 p1=p1, p2=p2, |
600 p1=p1, p2=p2, extra=extra, |
662 extra=extra, |
601 empty_ok=empty_ok) |
663 empty_ok=empty_ok) |
|
664 except TypeError: |
602 except TypeError: |
665 return super(kwrepo, self).commit(files=files, text=text, |
603 return super(kwrepo, |
666 user=user, date=date, |
604 self).commit(files, text, user, date, |
667 match=match, force=force, |
605 match, force, |
668 force_editor=force_editor, |
606 force_editor=force_editor, |
669 p1=p1, p2=p2, extra=extra) |
607 p1=p1, p2=p2, extra=extra) |
670 |
608 |
671 def commit(self, files=None, text='', user=None, date=None, |
609 def commit(self, files=None, text='', user=None, date=None, |
672 match=util.always, force=False, lock=None, wlock=None, |
610 match=util.always, force=False, lock=None, wlock=None, |
673 force_editor=False, p1=None, p2=None, extra={}, |
611 force_editor=False, p1=None, p2=None, extra={}, |
674 empty_ok=False): |
612 empty_ok=False): |
700 if _p2 == nullid: |
638 if _p2 == nullid: |
701 _p2 = '' |
639 _p2 = '' |
702 else: |
640 else: |
703 _p2 = hex(_p2) |
641 _p2 = hex(_p2) |
704 |
642 |
705 node = self._commit(files, text, user, date, match, force, |
643 n = self._commit(files, text, user, date, match, force, _lock, |
706 _lock, _wlock, force_editor, p1, p2, extra, |
644 _wlock, force_editor, p1, p2, extra, empty_ok) |
707 empty_ok) |
|
708 |
645 |
709 # restore commit hooks |
646 # restore commit hooks |
710 for name, cmd in commithooks.iteritems(): |
647 for name, cmd in commithooks.iteritems(): |
711 ui.setconfig('hooks', name, cmd) |
648 ui.setconfig('hooks', name, cmd) |
712 if node is not None: |
649 if n is not None: |
713 kwt.overwrite(node=node) |
650 kwt.overwrite(n, True, None) |
714 repo.hook('commit', node=node, parent1=_p1, parent2=_p2) |
651 repo.hook('commit', node=n, parent1=_p1, parent2=_p2) |
715 return node |
652 return n |
716 finally: |
653 finally: |
717 del _wlock, _lock |
654 del _wlock, _lock |
718 |
655 |
719 repo.__class__ = kwrepo |
656 repo.__class__ = kwrepo |
720 patch.diff = _kw_diff |
657 |
721 if hasattr(patch, 'patchfile'): |
658 # monkeypatches |
722 patch.patchfile.__init__ = _kwpatchfile_init |
659 try: |
|
660 # avoid spurious rejects if patchfile is available |
|
661 def kwpatchfile_init(self, ui, fname, missing=False): |
|
662 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid |
|
663 rejects or conflicts due to expanded keywords in working dir.''' |
|
664 try: |
|
665 patchfile_init(self, ui, fname, missing) |
|
666 except TypeError: |
|
667 # "missing" arg added in e90e72c6b4c7 |
|
668 patchfile_init(self, ui, fname) |
|
669 self.lines = kwt.shrinklines(self.fname, self.lines) |
|
670 |
|
671 patchfile_init = patch.patchfile.__init__ |
|
672 patch.patchfile.__init__ = kwpatchfile_init |
|
673 except AttributeError: |
|
674 pass |
|
675 |
|
676 def kw_diff(repo, node1=None, node2=None, files=None, match=util.always, |
|
677 fp=None, changes=None, opts=None): |
|
678 # only expand if comparing against working dir |
|
679 if node2 is not None: |
|
680 kwt.matcher = util.never |
|
681 elif node1 is not None and node1 != repo.changectx().node(): |
|
682 kwt.restrict = True |
|
683 patch_diff(repo, node1, node2, files, match, fp, changes, opts) |
|
684 |
|
685 patch_diff = patch.diff |
|
686 patch.diff = kw_diff |
|
687 |
|
688 try: |
|
689 from mercurial.hgweb import webcommands |
|
690 def kwweb_changeset(web, req, tmpl): |
|
691 '''Wraps webcommands.changeset turning off keyword expansion.''' |
|
692 kwt.matcher = util.never |
|
693 return webcommands_changeset(web, req, tmpl) |
|
694 |
|
695 def kwweb_filediff(web, req, tmpl): |
|
696 '''Wraps webcommands.filediff turning off keyword expansion.''' |
|
697 kwt.matcher = util.never |
|
698 return webcommands_filediff(web, req, tmpl) |
|
699 |
|
700 webcommands_changeset = webcommands.changeset |
|
701 webcommands_filediff = webcommands.filediff |
|
702 webcommands.changeset = webcommands.rev = kwweb_changeset |
|
703 webcommands.filediff = webcommands.diff = kwweb_filediff |
|
704 |
|
705 except ImportError: |
|
706 from mercurial.hgweb.hgweb_mod import hgweb |
|
707 def kwweb_do_changeset(self, req): |
|
708 kwt.matcher = util.never |
|
709 hgweb_do_changeset(self, req) |
|
710 |
|
711 def kwweb_do_filediff(self, req): |
|
712 kwt.matcher = util.never |
|
713 hgweb_do_filediff(self, req) |
|
714 |
|
715 hgweb_do_changeset = hgweb.do_changeset |
|
716 hgweb_do_filediff = hgweb.do_filediff |
|
717 hgweb.do_changeset = hgweb.do_rev = kwweb_do_changeset |
|
718 hgweb.do_filediff = hgweb.do_diff = kwweb_do_filediff |
723 |
719 |
724 |
720 |
725 cmdtable = { |
721 cmdtable = { |
726 'kwdemo': |
722 'kwdemo': |
727 (demo, |
723 (demo, |