82 |
82 |
83 from mercurial import commands, cmdutil, context, dispatch, filelog, revlog |
83 from mercurial import commands, cmdutil, context, dispatch, filelog, revlog |
84 from mercurial import patch, localrepo, templater, templatefilters, util |
84 from mercurial import patch, localrepo, templater, templatefilters, util |
85 from mercurial.node import * |
85 from mercurial.node import * |
86 from mercurial.i18n import _ |
86 from mercurial.i18n import _ |
87 import re, shutil, sys, tempfile, time |
87 import re, shutil, tempfile, time |
88 |
88 |
89 commands.optionalrepo += ' kwdemo' |
89 commands.optionalrepo += ' kwdemo' |
90 |
90 |
91 # hg commands that do not act on keywords |
91 # hg commands that do not act on keywords |
92 nokwcommands = ('add addremove bundle copy export grep identify incoming init' |
92 nokwcommands = ('add addremove bundle copy export grep identify incoming init' |
93 ' log outgoing push remove rename rollback tip convert') |
93 ' log outgoing push remove rename rollback tip convert email') |
94 |
94 |
95 # hg commands that trigger expansion only when writing to working dir, |
95 # hg commands that trigger expansion only when writing to working dir, |
96 # not when reading filelog, and unexpand when reading from working dir |
96 # not when reading filelog, and unexpand when reading from working dir |
97 restricted = 'diff1 record qfold qimport qnew qpush qrefresh qrecord' |
97 restricted = 'diff1 record qfold qimport qnew qpush qrefresh qrecord' |
98 |
98 |
99 def utcdate(date): |
99 def utcdate(date): |
100 '''Returns hgdate in cvs-like UTC format.''' |
100 '''Returns hgdate in cvs-like UTC format.''' |
101 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0])) |
101 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0])) |
102 |
102 |
103 |
103 |
104 _kwtemplater = None |
104 _kwtemplater, _cmd, _cmdoptions = None, None, None |
|
105 |
|
106 # store originals of monkeypatches |
|
107 _patchfile_init = patch.patchfile.__init__ |
|
108 _dispatch_parse = dispatch._parse |
|
109 |
|
110 def _kwpatchfile_init(self, ui, fname, missing=False): |
|
111 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid |
|
112 rejects or conflicts due to expanded keywords in working dir.''' |
|
113 _patchfile_init(self, ui, fname, missing=missing) |
|
114 if _kwtemplater.matcher(self.fname): |
|
115 # shrink keywords read from working dir |
|
116 kwshrunk = _kwtemplater.shrink(''.join(self.lines)) |
|
117 self.lines = kwshrunk.splitlines(True) |
|
118 |
|
119 def _kwdispatch_parse(ui, args): |
|
120 '''Monkeypatch dispatch._parse to obtain |
|
121 current command and command options (global _cmd, _cmdoptions).''' |
|
122 global _cmd, _cmdoptions |
|
123 _cmd, func, args, options, _cmdoptions = _dispatch_parse(ui, args) |
|
124 return _cmd, func, args, options, _cmdoptions |
|
125 |
|
126 dispatch._parse = _kwdispatch_parse |
|
127 |
105 |
128 |
106 class kwtemplater(object): |
129 class kwtemplater(object): |
107 ''' |
130 ''' |
108 Sets up keyword templates, corresponding keyword regex, and |
131 Sets up keyword templates, corresponding keyword regex, and |
109 provides keyword substitution functions. |
132 provides keyword substitution functions. |
116 'Source': '{root}/{file},v', |
139 'Source': '{root}/{file},v', |
117 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}', |
140 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}', |
118 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}', |
141 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}', |
119 } |
142 } |
120 |
143 |
121 def __init__(self, ui, repo, inc, exc, restricted): |
144 def __init__(self, ui, repo, inc, exc, restrict): |
122 self.ui = ui |
145 self.ui = ui |
123 self.repo = repo |
146 self.repo = repo |
124 self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1] |
147 self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1] |
125 self.restricted = restricted |
148 self.restrict = restrict |
126 self.commitnode = None |
149 self.commitnode = None |
127 self.path = '' |
150 self.path = '' |
128 |
151 |
129 kwmaps = self.ui.configitems('keywordmaps') |
152 kwmaps = self.ui.configitems('keywordmaps') |
130 if kwmaps: # override default templates |
153 if kwmaps: # override default templates |
159 |
182 |
160 return subfunc(kwsub, data) |
183 return subfunc(kwsub, data) |
161 |
184 |
162 def expand(self, node, data): |
185 def expand(self, node, data): |
163 '''Returns data with keywords expanded.''' |
186 '''Returns data with keywords expanded.''' |
164 if self.restricted or util.binary(data): |
187 if self.restrict or util.binary(data): |
165 return data |
188 return data |
166 return self.substitute(node, data, self.re_kw.sub) |
189 return self.substitute(node, data, self.re_kw.sub) |
167 |
190 |
168 def process(self, node, data, expand): |
191 def process(self, node, data, expand): |
169 '''Returns a tuple: data, count. |
192 '''Returns a tuple: data, count. |
210 text = _kwtemplater.shrink(text) |
233 text = _kwtemplater.shrink(text) |
211 if self.renamed(node): |
234 if self.renamed(node): |
212 t2 = super(kwfilelog, self).read(node) |
235 t2 = super(kwfilelog, self).read(node) |
213 return t2 != text |
236 return t2 != text |
214 return revlog.revlog.cmp(self, node, text) |
237 return revlog.revlog.cmp(self, node, text) |
215 |
|
216 |
|
217 # store original patch.patchfile.__init__ |
|
218 _patchfile_init = patch.patchfile.__init__ |
|
219 |
|
220 def _kwpatchfile_init(self, ui, fname, missing=False): |
|
221 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid |
|
222 rejects or conflicts due to expanded keywords in working dir.''' |
|
223 _patchfile_init(self, ui, fname, missing=missing) |
|
224 |
|
225 if _kwtemplater.matcher(self.fname): |
|
226 # shrink keywords read from working dir |
|
227 kwshrunk = _kwtemplater.shrink(''.join(self.lines)) |
|
228 self.lines = kwshrunk.splitlines(True) |
|
229 |
|
230 |
238 |
231 def _iskwfile(f, link): |
239 def _iskwfile(f, link): |
232 return not link(f) and _kwtemplater.matcher(f) |
240 return not link(f) and _kwtemplater.matcher(f) |
233 |
241 |
234 def _status(ui, repo, *pats, **opts): |
242 def _status(ui, repo, *pats, **opts): |
410 Wraps commit to overwrite configured files with updated |
418 Wraps commit to overwrite configured files with updated |
411 keyword substitutions. |
419 keyword substitutions. |
412 This is done for local repos only, and only if there are |
420 This is done for local repos only, and only if there are |
413 files configured at all for keyword substitution.''' |
421 files configured at all for keyword substitution.''' |
414 |
422 |
415 if not repo.local(): |
423 global _kwtemplater |
416 return |
424 hgcmd, hgcmdopts = _cmd, _cmdoptions |
417 |
425 |
418 hgcmd, func, args, opts, cmdopts = dispatch._parse(ui, sys.argv[1:]) |
426 try: |
419 if hgcmd in nokwcommands.split(): |
427 if (not repo.local() or hgcmd in nokwcommands.split() |
420 return |
428 or '.hg' in repo.root.split('/') |
421 |
429 or repo._url.startswith('bundle:')): |
422 if hgcmd == 'diff': |
|
423 # only expand if comparing against working dir |
|
424 node1, node2 = cmdutil.revpair(repo, cmdopts.get('rev')) |
|
425 if node2 is not None: |
|
426 return |
430 return |
427 # shrink if rev is not current node |
431 except AttributeError: |
428 if node1 is not None and node1 != repo.changectx().node(): |
432 pass |
429 hgcmd = 'diff1' |
433 |
430 |
434 inc, exc = [], ['.hg*'] |
431 inc, exc = [], ['.hgtags'] |
|
432 for pat, opt in ui.configitems('keyword'): |
435 for pat, opt in ui.configitems('keyword'): |
433 if opt != 'ignore': |
436 if opt != 'ignore': |
434 inc.append(pat) |
437 inc.append(pat) |
435 else: |
438 else: |
436 exc.append(pat) |
439 exc.append(pat) |
437 if not inc: |
440 if not inc: |
438 return |
441 return |
439 |
442 |
440 global _kwtemplater |
443 if hgcmd == 'diff': |
441 _restricted = hgcmd in restricted.split() |
444 # only expand if comparing against working dir |
442 _kwtemplater = kwtemplater(ui, repo, inc, exc, _restricted) |
445 node1, node2 = cmdutil.revpair(repo, hgcmdopts.get('rev')) |
|
446 if node2 is not None: |
|
447 return |
|
448 # shrink if rev is not current node |
|
449 if node1 is not None and node1 != repo.changectx().node(): |
|
450 hgcmd = 'diff1' |
|
451 |
|
452 restrict = hgcmd in restricted.split() |
|
453 _kwtemplater = kwtemplater(ui, repo, inc, exc, restrict) |
443 |
454 |
444 class kwrepo(repo.__class__): |
455 class kwrepo(repo.__class__): |
445 def file(self, f, kwmatch=False): |
456 def file(self, f, kwmatch=False): |
446 if f[0] == '/': |
457 if f[0] == '/': |
447 f = f[1:] |
458 f = f[1:] |
449 return kwfilelog(self.sopener, f) |
460 return kwfilelog(self.sopener, f) |
450 return filelog.filelog(self.sopener, f) |
461 return filelog.filelog(self.sopener, f) |
451 |
462 |
452 def wread(self, filename): |
463 def wread(self, filename): |
453 data = super(kwrepo, self).wread(filename) |
464 data = super(kwrepo, self).wread(filename) |
454 if _restricted and _kwtemplater.matcher(filename): |
465 if restrict and _kwtemplater.matcher(filename): |
455 return _kwtemplater.shrink(data) |
466 return _kwtemplater.shrink(data) |
456 return data |
467 return data |
457 |
468 |
458 def commit(self, files=None, text='', user=None, date=None, |
469 def commit(self, files=None, text='', user=None, date=None, |
459 match=util.always, force=False, force_editor=False, |
470 match=util.always, force=False, force_editor=False, |