156 |
156 |
157 configtable = {} |
157 configtable = {} |
158 configitem = registrar.configitem(configtable) |
158 configitem = registrar.configitem(configtable) |
159 |
159 |
160 configitem( |
160 configitem( |
161 b'keywordset', b'svn', default=False, |
161 b'keywordset', |
|
162 b'svn', |
|
163 default=False, |
162 ) |
164 ) |
163 # date like in cvs' $Date |
165 # date like in cvs' $Date |
164 @templatefilter(b'utcdate', intype=templateutil.date) |
166 @templatefilter(b'utcdate', intype=templateutil.date) |
165 def utcdate(date): |
167 def utcdate(date): |
166 '''Date. Returns a UTC-date in this format: "2009/08/18 11:00:13". |
168 """Date. Returns a UTC-date in this format: "2009/08/18 11:00:13".""" |
167 ''' |
|
168 dateformat = b'%Y/%m/%d %H:%M:%S' |
169 dateformat = b'%Y/%m/%d %H:%M:%S' |
169 return dateutil.datestr((date[0], 0), dateformat) |
170 return dateutil.datestr((date[0], 0), dateformat) |
170 |
171 |
171 |
172 |
172 # date like in svn's $Date |
173 # date like in svn's $Date |
173 @templatefilter(b'svnisodate', intype=templateutil.date) |
174 @templatefilter(b'svnisodate', intype=templateutil.date) |
174 def svnisodate(date): |
175 def svnisodate(date): |
175 '''Date. Returns a date in this format: "2009-08-18 13:00:13 |
176 """Date. Returns a date in this format: "2009-08-18 13:00:13 |
176 +0200 (Tue, 18 Aug 2009)". |
177 +0200 (Tue, 18 Aug 2009)". |
177 ''' |
178 """ |
178 return dateutil.datestr(date, b'%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)') |
179 return dateutil.datestr(date, b'%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)') |
179 |
180 |
180 |
181 |
181 # date like in svn's $Id |
182 # date like in svn's $Id |
182 @templatefilter(b'svnutcdate', intype=templateutil.date) |
183 @templatefilter(b'svnutcdate', intype=templateutil.date) |
183 def svnutcdate(date): |
184 def svnutcdate(date): |
184 '''Date. Returns a UTC-date in this format: "2009-08-18 |
185 """Date. Returns a UTC-date in this format: "2009-08-18 |
185 11:00:13Z". |
186 11:00:13Z". |
186 ''' |
187 """ |
187 dateformat = b'%Y-%m-%d %H:%M:%SZ' |
188 dateformat = b'%Y-%m-%d %H:%M:%SZ' |
188 return dateutil.datestr((date[0], 0), dateformat) |
189 return dateutil.datestr((date[0], 0), dateformat) |
189 |
190 |
190 |
191 |
191 # make keyword tools accessible |
192 # make keyword tools accessible |
219 templates.update(kwsets[ui.configbool(b'keywordset', b'svn')]) |
220 templates.update(kwsets[ui.configbool(b'keywordset', b'svn')]) |
220 return templates |
221 return templates |
221 |
222 |
222 |
223 |
223 def _shrinktext(text, subfunc): |
224 def _shrinktext(text, subfunc): |
224 '''Helper for keyword expansion removal in text. |
225 """Helper for keyword expansion removal in text. |
225 Depending on subfunc also returns number of substitutions.''' |
226 Depending on subfunc also returns number of substitutions.""" |
226 return subfunc(br'$\1$', text) |
227 return subfunc(br'$\1$', text) |
227 |
228 |
228 |
229 |
229 def _preselect(wstatus, changed): |
230 def _preselect(wstatus, changed): |
230 '''Retrieves modified and added files from a working directory state |
231 """Retrieves modified and added files from a working directory state |
231 and returns the subset of each contained in given changed files |
232 and returns the subset of each contained in given changed files |
232 retrieved from a change context.''' |
233 retrieved from a change context.""" |
233 modified = [f for f in wstatus.modified if f in changed] |
234 modified = [f for f in wstatus.modified if f in changed] |
234 added = [f for f in wstatus.added if f in changed] |
235 added = [f for f in wstatus.added if f in changed] |
235 return modified, added |
236 return modified, added |
236 |
237 |
237 |
238 |
238 class kwtemplater(object): |
239 class kwtemplater(object): |
239 ''' |
240 """ |
240 Sets up keyword templates, corresponding keyword regex, and |
241 Sets up keyword templates, corresponding keyword regex, and |
241 provides keyword substitution functions. |
242 provides keyword substitution functions. |
242 ''' |
243 """ |
243 |
244 |
244 def __init__(self, ui, repo, inc, exc): |
245 def __init__(self, ui, repo, inc, exc): |
245 self.ui = ui |
246 self.ui = ui |
246 self._repo = weakref.ref(repo) |
247 self._repo = weakref.ref(repo) |
247 self.match = match.match(repo.root, b'', [], inc, exc) |
248 self.match = match.match(repo.root, b'', [], inc, exc) |
302 ctx = self.linkctx(path, node) |
303 ctx = self.linkctx(path, node) |
303 return self.substitute(data, path, ctx, self.rekw.sub) |
304 return self.substitute(data, path, ctx, self.rekw.sub) |
304 return data |
305 return data |
305 |
306 |
306 def iskwfile(self, cand, ctx): |
307 def iskwfile(self, cand, ctx): |
307 '''Returns subset of candidates which are configured for keyword |
308 """Returns subset of candidates which are configured for keyword |
308 expansion but are not symbolic links.''' |
309 expansion but are not symbolic links.""" |
309 return [f for f in cand if self.match(f) and b'l' not in ctx.flags(f)] |
310 return [f for f in cand if self.match(f) and b'l' not in ctx.flags(f)] |
310 |
311 |
311 def overwrite(self, ctx, candidates, lookup, expand, rekw=False): |
312 def overwrite(self, ctx, candidates, lookup, expand, rekw=False): |
312 '''Overwrites selected files expanding/shrinking keywords.''' |
313 '''Overwrites selected files expanding/shrinking keywords.''' |
313 if self.restrict or lookup or self.postcommit: # exclude kw_copy |
314 if self.restrict or lookup or self.postcommit: # exclude kw_copy |
372 if not stringutil.binary(text): |
373 if not stringutil.binary(text): |
373 return _shrinktext(text, self.rekwexp.sub).splitlines(True) |
374 return _shrinktext(text, self.rekwexp.sub).splitlines(True) |
374 return lines |
375 return lines |
375 |
376 |
376 def wread(self, fname, data): |
377 def wread(self, fname, data): |
377 '''If in restricted mode returns data read from wdir with |
378 """If in restricted mode returns data read from wdir with |
378 keyword substitutions removed.''' |
379 keyword substitutions removed.""" |
379 if self.restrict: |
380 if self.restrict: |
380 return self.shrink(fname, data) |
381 return self.shrink(fname, data) |
381 return data |
382 return data |
382 |
383 |
383 |
384 |
384 class kwfilelog(filelog.filelog): |
385 class kwfilelog(filelog.filelog): |
385 ''' |
386 """ |
386 Subclass of filelog to hook into its read, add, cmp methods. |
387 Subclass of filelog to hook into its read, add, cmp methods. |
387 Keywords are "stored" unexpanded, and processed on reading. |
388 Keywords are "stored" unexpanded, and processed on reading. |
388 ''' |
389 """ |
389 |
390 |
390 def __init__(self, opener, kwt, path): |
391 def __init__(self, opener, kwt, path): |
391 super(kwfilelog, self).__init__(opener, path) |
392 super(kwfilelog, self).__init__(opener, path) |
392 self.kwt = kwt |
393 self.kwt = kwt |
393 self.path = path |
394 self.path = path |
409 text = self.kwt.shrink(self.path, text) |
410 text = self.kwt.shrink(self.path, text) |
410 return super(kwfilelog, self).cmp(node, text) |
411 return super(kwfilelog, self).cmp(node, text) |
411 |
412 |
412 |
413 |
413 def _status(ui, repo, wctx, kwt, *pats, **opts): |
414 def _status(ui, repo, wctx, kwt, *pats, **opts): |
414 '''Bails out if [keyword] configuration is not active. |
415 """Bails out if [keyword] configuration is not active. |
415 Returns status of working directory.''' |
416 Returns status of working directory.""" |
416 if kwt: |
417 if kwt: |
417 opts = pycompat.byteskwargs(opts) |
418 opts = pycompat.byteskwargs(opts) |
418 return repo.status( |
419 return repo.status( |
419 match=scmutil.match(wctx, pats, opts), |
420 match=scmutil.match(wctx, pats, opts), |
420 clean=True, |
421 clean=True, |
446 ], |
447 ], |
447 _(b'hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...'), |
448 _(b'hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...'), |
448 optionalrepo=True, |
449 optionalrepo=True, |
449 ) |
450 ) |
450 def demo(ui, repo, *args, **opts): |
451 def demo(ui, repo, *args, **opts): |
451 '''print [keywordmaps] configuration and an expansion example |
452 """print [keywordmaps] configuration and an expansion example |
452 |
453 |
453 Show current, custom, or default keyword template maps and their |
454 Show current, custom, or default keyword template maps and their |
454 expansions. |
455 expansions. |
455 |
456 |
456 Extend the current configuration by specifying maps as arguments |
457 Extend the current configuration by specifying maps as arguments |
457 and using -f/--rcfile to source an external hgrc file. |
458 and using -f/--rcfile to source an external hgrc file. |
458 |
459 |
459 Use -d/--default to disable current configuration. |
460 Use -d/--default to disable current configuration. |
460 |
461 |
461 See :hg:`help templates` for information on templates and filters. |
462 See :hg:`help templates` for information on templates and filters. |
462 ''' |
463 """ |
463 |
464 |
464 def demoitems(section, items): |
465 def demoitems(section, items): |
465 ui.write(b'[%s]\n' % section) |
466 ui.write(b'[%s]\n' % section) |
466 for k, v in sorted(items): |
467 for k, v in sorted(items): |
467 if isinstance(v, bool): |
468 if isinstance(v, bool): |
545 cmdutil.walkopts, |
546 cmdutil.walkopts, |
546 _(b'hg kwexpand [OPTION]... [FILE]...'), |
547 _(b'hg kwexpand [OPTION]... [FILE]...'), |
547 inferrepo=True, |
548 inferrepo=True, |
548 ) |
549 ) |
549 def expand(ui, repo, *pats, **opts): |
550 def expand(ui, repo, *pats, **opts): |
550 '''expand keywords in the working directory |
551 """expand keywords in the working directory |
551 |
552 |
552 Run after (re)enabling keyword expansion. |
553 Run after (re)enabling keyword expansion. |
553 |
554 |
554 kwexpand refuses to run if given files contain local changes. |
555 kwexpand refuses to run if given files contain local changes. |
555 ''' |
556 """ |
556 # 3rd argument sets expansion to True |
557 # 3rd argument sets expansion to True |
557 _kwfwrite(ui, repo, True, *pats, **opts) |
558 _kwfwrite(ui, repo, True, *pats, **opts) |
558 |
559 |
559 |
560 |
560 @command( |
561 @command( |
567 + cmdutil.walkopts, |
568 + cmdutil.walkopts, |
568 _(b'hg kwfiles [OPTION]... [FILE]...'), |
569 _(b'hg kwfiles [OPTION]... [FILE]...'), |
569 inferrepo=True, |
570 inferrepo=True, |
570 ) |
571 ) |
571 def files(ui, repo, *pats, **opts): |
572 def files(ui, repo, *pats, **opts): |
572 '''show files configured for keyword expansion |
573 """show files configured for keyword expansion |
573 |
574 |
574 List which files in the working directory are matched by the |
575 List which files in the working directory are matched by the |
575 [keyword] configuration patterns. |
576 [keyword] configuration patterns. |
576 |
577 |
577 Useful to prevent inadvertent keyword expansion and to speed up |
578 Useful to prevent inadvertent keyword expansion and to speed up |
586 |
587 |
587 K = keyword expansion candidate |
588 K = keyword expansion candidate |
588 k = keyword expansion candidate (not tracked) |
589 k = keyword expansion candidate (not tracked) |
589 I = ignored |
590 I = ignored |
590 i = ignored (not tracked) |
591 i = ignored (not tracked) |
591 ''' |
592 """ |
592 kwt = getattr(repo, '_keywordkwt', None) |
593 kwt = getattr(repo, '_keywordkwt', None) |
593 wctx = repo[None] |
594 wctx = repo[None] |
594 status = _status(ui, repo, wctx, kwt, *pats, **opts) |
595 status = _status(ui, repo, wctx, kwt, *pats, **opts) |
595 if pats: |
596 if pats: |
596 cwd = repo.getcwd() |
597 cwd = repo.getcwd() |
632 cmdutil.walkopts, |
633 cmdutil.walkopts, |
633 _(b'hg kwshrink [OPTION]... [FILE]...'), |
634 _(b'hg kwshrink [OPTION]... [FILE]...'), |
634 inferrepo=True, |
635 inferrepo=True, |
635 ) |
636 ) |
636 def shrink(ui, repo, *pats, **opts): |
637 def shrink(ui, repo, *pats, **opts): |
637 '''revert expanded keywords in the working directory |
638 """revert expanded keywords in the working directory |
638 |
639 |
639 Must be run before changing/disabling active keywords. |
640 Must be run before changing/disabling active keywords. |
640 |
641 |
641 kwshrink refuses to run if given files contain local changes. |
642 kwshrink refuses to run if given files contain local changes. |
642 ''' |
643 """ |
643 # 3rd argument sets expansion to False |
644 # 3rd argument sets expansion to False |
644 _kwfwrite(ui, repo, False, *pats, **opts) |
645 _kwfwrite(ui, repo, False, *pats, **opts) |
645 |
646 |
646 |
647 |
647 # monkeypatches |
648 # monkeypatches |
648 |
649 |
649 |
650 |
650 def kwpatchfile_init(orig, self, ui, gp, backend, store, eolmode=None): |
651 def kwpatchfile_init(orig, self, ui, gp, backend, store, eolmode=None): |
651 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid |
652 """Monkeypatch/wrap patch.patchfile.__init__ to avoid |
652 rejects or conflicts due to expanded keywords in working dir.''' |
653 rejects or conflicts due to expanded keywords in working dir.""" |
653 orig(self, ui, gp, backend, store, eolmode) |
654 orig(self, ui, gp, backend, store, eolmode) |
654 kwt = getattr(getattr(backend, 'repo', None), '_keywordkwt', None) |
655 kwt = getattr(getattr(backend, 'repo', None), '_keywordkwt', None) |
655 if kwt: |
656 if kwt: |
656 # shrink keywords read from working dir |
657 # shrink keywords read from working dir |
657 self.lines = kwt.shrinklines(self.fname, self.lines) |
658 self.lines = kwt.shrinklines(self.fname, self.lines) |
700 kwt.restrict = False |
701 kwt.restrict = False |
701 return newid |
702 return newid |
702 |
703 |
703 |
704 |
704 def kw_copy(orig, ui, repo, pats, opts, rename=False): |
705 def kw_copy(orig, ui, repo, pats, opts, rename=False): |
705 '''Wraps cmdutil.copy so that copy/rename destinations do not |
706 """Wraps cmdutil.copy so that copy/rename destinations do not |
706 contain expanded keywords. |
707 contain expanded keywords. |
707 Note that the source of a regular file destination may also be a |
708 Note that the source of a regular file destination may also be a |
708 symlink: |
709 symlink: |
709 hg cp sym x -> x is symlink |
710 hg cp sym x -> x is symlink |
710 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords) |
711 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords) |
711 For the latter we have to follow the symlink to find out whether its |
712 For the latter we have to follow the symlink to find out whether its |
712 target is configured for expansion and we therefore must unexpand the |
713 target is configured for expansion and we therefore must unexpand the |
713 keywords in the destination.''' |
714 keywords in the destination.""" |
714 kwt = getattr(repo, '_keywordkwt', None) |
715 kwt = getattr(repo, '_keywordkwt', None) |
715 if kwt is None: |
716 if kwt is None: |
716 return orig(ui, repo, pats, opts, rename) |
717 return orig(ui, repo, pats, opts, rename) |
717 with repo.wlock(): |
718 with repo.wlock(): |
718 orig(ui, repo, pats, opts, rename) |
719 orig(ui, repo, pats, opts, rename) |
720 return |
721 return |
721 wctx = repo[None] |
722 wctx = repo[None] |
722 cwd = repo.getcwd() |
723 cwd = repo.getcwd() |
723 |
724 |
724 def haskwsource(dest): |
725 def haskwsource(dest): |
725 '''Returns true if dest is a regular file and configured for |
726 """Returns true if dest is a regular file and configured for |
726 expansion or a symlink which points to a file configured for |
727 expansion or a symlink which points to a file configured for |
727 expansion. ''' |
728 expansion.""" |
728 source = repo.dirstate.copied(dest) |
729 source = repo.dirstate.copied(dest) |
729 if b'l' in wctx.flags(source): |
730 if b'l' in wctx.flags(source): |
730 source = pathutil.canonpath( |
731 source = pathutil.canonpath( |
731 repo.root, cwd, os.path.realpath(source) |
732 repo.root, cwd, os.path.realpath(source) |
732 ) |
733 ) |
783 return self._filelog.cmp(self._filenode, fctx.data()) |
784 return self._filelog.cmp(self._filenode, fctx.data()) |
784 return True |
785 return True |
785 |
786 |
786 |
787 |
787 def uisetup(ui): |
788 def uisetup(ui): |
788 ''' Monkeypatches dispatch._parse to retrieve user command. |
789 """Monkeypatches dispatch._parse to retrieve user command. |
789 Overrides file method to return kwfilelog instead of filelog |
790 Overrides file method to return kwfilelog instead of filelog |
790 if file matches user configuration. |
791 if file matches user configuration. |
791 Wraps commit to overwrite configured files with updated |
792 Wraps commit to overwrite configured files with updated |
792 keyword substitutions. |
793 keyword substitutions. |
793 Monkeypatches patch and webcommands.''' |
794 Monkeypatches patch and webcommands.""" |
794 |
795 |
795 def kwdispatch_parse(orig, ui, args): |
796 def kwdispatch_parse(orig, ui, args): |
796 '''Monkeypatch dispatch._parse to obtain running hg command.''' |
797 '''Monkeypatch dispatch._parse to obtain running hg command.''' |
797 cmd, func, args, options, cmdoptions = orig(ui, args) |
798 cmd, func, args, options, cmdoptions = orig(ui, args) |
798 kwtools[b'hgcmd'] = cmd |
799 kwtools[b'hgcmd'] = cmd |