138 else: |
138 else: |
139 commands.parse = _kwdispatch_parse |
139 commands.parse = _kwdispatch_parse |
140 |
140 |
141 try: |
141 try: |
142 # avoid spurious rejects if patchfile is available |
142 # avoid spurious rejects if patchfile is available |
143 from mercurial.patch import patchfile |
143 _patchfile_init = patch.patchfile.__init__ |
144 _patchfile_init = patchfile.__init__ |
|
145 |
144 |
146 def _kwpatchfile_init(self, ui, fname, missing=False): |
145 def _kwpatchfile_init(self, ui, fname, missing=False): |
147 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid |
146 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid |
148 rejects or conflicts due to expanded keywords in working dir.''' |
147 rejects or conflicts due to expanded keywords in working dir.''' |
149 try: |
148 try: |
150 _patchfile_init(self, ui, fname, missing=missing) |
149 _patchfile_init(self, ui, fname, missing=missing) |
151 except TypeError: |
150 except TypeError: |
152 # "missing" arg added in e90e72c6b4c7 |
151 # "missing" arg added in e90e72c6b4c7 |
153 _patchfile_init(self, ui, fname) |
152 _patchfile_init(self, ui, fname) |
154 if _kwtemplater.matcher(self.fname): |
153 self.lines = kwx['templater'].shrinklines(self.fname, self.lines) |
155 # shrink keywords read from working dir |
154 except AttributeError: |
156 kwshrunk = _kwtemplater.shrink(''.join(self.lines)) |
|
157 self.lines = kwshrunk.splitlines(True) |
|
158 except ImportError: |
|
159 pass |
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 kwx['templater'].matcher = util.never |
|
163 elif node1 is not None and node1 != repo.changectx().node(): |
|
164 kwx['templater'].restrict = True |
|
165 _patch_diff(repo, node1=node1, node2=node2, files=files, match=match, |
|
166 fp=fp, changes=changes, opts=opts) |
160 |
167 |
161 try: |
168 try: |
162 from mercurial.hgweb import webcommands |
169 from mercurial.hgweb import webcommands |
163 |
170 |
164 def _kwweb_changeset(web, req, tmpl): |
171 def _kwweb_changeset(web, req, tmpl): |
165 '''Wraps webcommands.changeset turning off keyword expansion.''' |
172 '''Wraps webcommands.changeset turning off keyword expansion.''' |
166 try: |
173 if kwx['templater']: |
167 _kwtemplater.matcher = util.never |
174 kwx['templater'].matcher = util.neve |
168 except AttributeError: |
|
169 pass |
|
170 return web.changeset(tmpl, web.changectx(req)) |
175 return web.changeset(tmpl, web.changectx(req)) |
171 |
176 |
172 def _kwweb_filediff(web, req, tmpl): |
177 def _kwweb_filediff(web, req, tmpl): |
173 '''Wraps webcommands.filediff turning off keyword expansion.''' |
178 '''Wraps webcommands.filediff turning off keyword expansion.''' |
174 try: |
179 if kwx['templater']: |
175 _kwtemplater.matcher = util.never |
180 kwx['templater'].matcher = util.neve |
176 except AttributeError: |
|
177 pass |
|
178 return web.filediff(tmpl, web.filectx(req)) |
181 return web.filediff(tmpl, web.filectx(req)) |
179 |
182 |
180 webcommands.changeset = webcommands.rev = _kwweb_changeset |
183 webcommands.changeset = webcommands.rev = _kwweb_changeset |
181 webcommands.filediff = webcommands.diff = _kwweb_filediff |
184 webcommands.filediff = webcommands.diff = _kwweb_filediff |
182 |
185 |
183 except ImportError: |
186 except ImportError: |
184 from mercurial.hgweb.hgweb_mod import hgweb |
187 from mercurial.hgweb.hgweb_mod import hgweb |
185 |
188 |
186 def _kwweb_do_changeset(self, req): |
189 def _kwweb_do_changeset(self, req): |
187 try: |
190 if kwx['templater']: |
188 _kwtemplater.matcher = util.never |
191 kwx['templater'].matcher = util.never |
189 except AttributeError: |
|
190 pass |
|
191 req.write(self.changeset(self.changectx(req))) |
192 req.write(self.changeset(self.changectx(req))) |
192 |
193 |
193 def _kwweb_do_filediff(self, req): |
194 def _kwweb_do_filediff(self, req): |
194 try: |
195 if kwx['templater']: |
195 _kwtemplater.matcher = util.never |
196 kwx['templater'].matcher = util.never |
196 except AttributeError: |
|
197 pass |
|
198 req.write(self.filediff(self.filectx(req))) |
197 req.write(self.filediff(self.filectx(req))) |
199 |
198 |
200 hgweb.do_changeset = hgweb.do_rev = _kwweb_do_changeset |
199 hgweb.do_changeset = hgweb.do_rev = _kwweb_do_changeset |
201 hgweb.do_filediff = hgweb.do_diff = _kwweb_do_filediff |
200 hgweb.do_filediff = hgweb.do_diff = _kwweb_do_filediff |
202 |
201 |
336 False, '', False) |
333 False, '', False) |
337 except TypeError: |
334 except TypeError: |
338 return cmdutil.changeset_templater(self.ui, self.repo, |
335 return cmdutil.changeset_templater(self.ui, self.repo, |
339 False, None, '', False) |
336 False, None, '', False) |
340 |
337 |
341 def substitute(self, node, data, subfunc): |
338 def getnode(self, path, fnode): |
342 '''Obtains file's changenode if commit node not given, |
339 '''Derives changenode from file context.''' |
343 and calls given substitution function.''' |
340 c = context.filectx(self.repo, path, fileid=fnode) |
344 if self.commitnode: |
341 return c.node() |
345 fnode = self.commitnode |
342 |
346 else: |
343 def substitute(self, data, path, node, subfunc): |
347 c = context.filectx(self.repo, self.path, fileid=node) |
344 '''Replaces keywords in data with expanded template.''' |
348 fnode = c.node() |
|
349 |
|
350 def kwsub(mobj): |
345 def kwsub(mobj): |
351 '''Substitutes keyword using corresponding template.''' |
|
352 kw = mobj.group(1) |
346 kw = mobj.group(1) |
353 self.ct.use_template(self.templates[kw]) |
347 self.ct.use_template(self.templates[kw]) |
354 self.ui.pushbuffer() |
348 self.ui.pushbuffer() |
355 self.ct.show(changenode=fnode, root=self.repo.root, file=self.path) |
349 self.ct.show(changenode=node, root=self.repo.root, file=path) |
356 return '$%s: %s $' % (kw, template_firstline(self.ui.popbuffer())) |
350 return '$%s: %s $' % (kw, template_firstline(self.ui.popbuffer())) |
357 |
|
358 return subfunc(kwsub, data) |
351 return subfunc(kwsub, data) |
359 |
352 |
360 def expand(self, node, data): |
353 def expand(self, path, node, data): |
361 '''Returns data with keywords expanded.''' |
354 '''Returns data with keywords expanded.''' |
362 if self.restrict or util.binary(data): |
355 if not self.restrict and self.matcher(path) and not util.binary(data): |
363 return data |
356 changenode = self.getnode(path, node) |
364 return self.substitute(node, data, self.re_kw.sub) |
357 return self.substitute(data, path, changenode, self.re_kw.sub) |
365 |
358 return data |
366 def process(self, node, data, expand): |
359 |
367 '''Returns a tuple: data, count. |
360 def iskwfile(self, path, islink): |
368 Count is number of keywords/keyword substitutions, |
361 '''Returns true if path matches [keyword] pattern |
369 telling caller whether to act on file containing data.''' |
362 and is not a symbolic link. |
370 if util.binary(data): |
363 Caveat: localrepository._link fails on Windows.''' |
371 return data, None |
364 return self.matcher(path) and not islink(path) |
372 if expand: |
365 |
373 return self.substitute(node, data, self.re_kw.subn) |
366 def overwrite(self, node=None, expand=True, files=None): |
374 return data, self.re_kw.search(data) |
367 '''Overwrites selected files expanding/shrinking keywords.''' |
375 |
368 ctx = self.repo.changectx(node) |
376 def shrink(self, text): |
369 mf = ctx.manifest() |
|
370 if node is not None: # commit |
|
371 files = [f for f in ctx.files() if f in mf] |
|
372 notify = self.ui.debug |
|
373 else: # kwexpand/kwshrink |
|
374 notify = self.ui.note |
|
375 candidates = [f for f in files if self.iskwfile(f, mf.linkf)] |
|
376 if candidates: |
|
377 self.restrict = True # do not expand when reading |
|
378 candidates.sort() |
|
379 action = expand and 'expanding' or 'shrinking' |
|
380 overwritten = [] |
|
381 for f in candidates: |
|
382 fp = self.repo.file(f) |
|
383 data = fp.read(mf[f]) |
|
384 if util.binary(data): |
|
385 continue |
|
386 if expand: |
|
387 changenode = node or self.getnode(f, mf[f]) |
|
388 data, found = self.substitute(data, f, changenode, |
|
389 self.re_kw.subn) |
|
390 else: |
|
391 found = self.re_kw.search(data) |
|
392 if found: |
|
393 notify(_('overwriting %s %s keywords\n') % (f, action)) |
|
394 self.repo.wwrite(f, data, mf.flags(f)) |
|
395 overwritten.append(f) |
|
396 _normal(self.repo, overwritten) |
|
397 self.restrict = False |
|
398 |
|
399 def shrinktext(self, text): |
|
400 '''Unconditionally removes all keyword substitutions from text.''' |
|
401 return self.re_kw.sub(r'$\1$', text) |
|
402 |
|
403 def shrink(self, fname, text): |
377 '''Returns text with all keyword substitutions removed.''' |
404 '''Returns text with all keyword substitutions removed.''' |
378 if util.binary(text): |
405 if self.matcher(fname) and not util.binary(text): |
379 return text |
406 return self.shrinktext(text) |
380 return self.re_kw.sub(r'$\1$', text) |
407 return text |
|
408 |
|
409 def shrinklines(self, fname, lines): |
|
410 '''Returns lines with keyword substitutions removed.''' |
|
411 if self.matcher(fname): |
|
412 text = ''.join(lines) |
|
413 if not util.binary(text): |
|
414 return self.shrinktext(text).splitlines(True) |
|
415 return lines |
|
416 |
|
417 def wread(self, fname, data): |
|
418 '''If in restricted mode returns data read from wdir with |
|
419 keyword substitutions removed.''' |
|
420 return self.restrict and self.shrink(fname, data) or data |
381 |
421 |
382 class kwfilelog(filelog.filelog): |
422 class kwfilelog(filelog.filelog): |
383 ''' |
423 ''' |
384 Subclass of filelog to hook into its read, add, cmp methods. |
424 Subclass of filelog to hook into its read, add, cmp methods. |
385 Keywords are "stored" unexpanded, and processed on reading. |
425 Keywords are "stored" unexpanded, and processed on reading. |
386 ''' |
426 ''' |
387 def __init__(self, opener, path): |
427 def __init__(self, opener, path): |
388 super(kwfilelog, self).__init__(opener, path) |
428 super(kwfilelog, self).__init__(opener, path) |
389 _kwtemplater.path = path |
429 self.kwt = kwx['templater'] |
390 |
430 self.path = path |
391 def kwctread(self, node, expand): |
|
392 '''Reads expanding and counting keywords, called from _overwrite.''' |
|
393 data = super(kwfilelog, self).read(node) |
|
394 return _kwtemplater.process(node, data, expand) |
|
395 |
431 |
396 def read(self, node): |
432 def read(self, node): |
397 '''Expands keywords when reading filelog.''' |
433 '''Expands keywords when reading filelog.''' |
398 data = super(kwfilelog, self).read(node) |
434 data = super(kwfilelog, self).read(node) |
399 return _kwtemplater.expand(node, data) |
435 return self.kwt.expand(self.path, node, data) |
400 |
436 |
401 def add(self, text, meta, tr, link, p1=None, p2=None): |
437 def add(self, text, meta, tr, link, p1=None, p2=None): |
402 '''Removes keyword substitutions when adding to filelog.''' |
438 '''Removes keyword substitutions when adding to filelog.''' |
403 text = _kwtemplater.shrink(text) |
439 text = self.kwt.shrink(self.path, text) |
404 return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2) |
440 return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2) |
405 |
441 |
406 def cmp(self, node, text): |
442 def cmp(self, node, text): |
407 '''Removes keyword substitutions for comparison.''' |
443 '''Removes keyword substitutions for comparison.''' |
408 text = _kwtemplater.shrink(text) |
444 text = self.kwt.shrink(self.path, text) |
409 if self.renamed(node): |
445 if self.renamed(node): |
410 t2 = super(kwfilelog, self).read(node) |
446 t2 = super(kwfilelog, self).read(node) |
411 return t2 != text |
447 return t2 != text |
412 return revlog.revlog.cmp(self, node, text) |
448 return revlog.revlog.cmp(self, node, text) |
413 |
449 |
414 def _iskwfile(f, link): |
450 def _status(ui, repo, kwt, *pats, **opts): |
415 return not link(f) and _kwtemplater.matcher(f) |
|
416 |
|
417 def _status(ui, repo, *pats, **opts): |
|
418 '''Bails out if [keyword] configuration is not active. |
451 '''Bails out if [keyword] configuration is not active. |
419 Returns status of working directory.''' |
452 Returns status of working directory.''' |
420 if _kwtemplater: |
453 if kwt: |
421 files, match, anypats = cmdutil.matchpats(repo, pats, opts) |
454 files, match, anypats = cmdutil.matchpats(repo, pats, opts) |
422 return repo.status(files=files, match=match, list_clean=True) |
455 return repo.status(files=files, match=match, list_clean=True) |
423 if ui.configitems('keyword'): |
456 if ui.configitems('keyword'): |
424 raise util.Abort(_('[keyword] patterns cannot match')) |
457 raise util.Abort(_('[keyword] patterns cannot match')) |
425 raise util.Abort(_('no [keyword] patterns configured')) |
458 raise util.Abort(_('no [keyword] patterns configured')) |
426 |
459 |
427 def _overwrite(ui, repo, node=None, expand=True, files=None): |
|
428 '''Overwrites selected files expanding/shrinking keywords.''' |
|
429 ctx = repo.changectx(node) |
|
430 mf = ctx.manifest() |
|
431 if node is not None: # commit |
|
432 _kwtemplater.commitnode = node |
|
433 files = [f for f in ctx.files() if f in mf] |
|
434 notify = ui.debug |
|
435 else: # kwexpand/kwshrink |
|
436 notify = ui.note |
|
437 candidates = [f for f in files if _iskwfile(f, mf.linkf)] |
|
438 if candidates: |
|
439 overwritten = [] |
|
440 candidates.sort() |
|
441 action = expand and 'expanding' or 'shrinking' |
|
442 for f in candidates: |
|
443 fp = repo.file(f, kwmatch=True) |
|
444 data, kwfound = fp.kwctread(mf[f], expand) |
|
445 if kwfound: |
|
446 notify(_('overwriting %s %s keywords\n') % (f, action)) |
|
447 _wwrite(repo, f, data, mf) |
|
448 overwritten.append(f) |
|
449 _normal(repo, overwritten) |
|
450 |
|
451 def _kwfwrite(ui, repo, expand, *pats, **opts): |
460 def _kwfwrite(ui, repo, expand, *pats, **opts): |
452 '''Selects files and passes them to _overwrite.''' |
461 '''Selects files and passes them to kwtemplater.overwrite.''' |
453 status = _status(ui, repo, *pats, **opts) |
462 kwt = kwx['templater'] |
|
463 status = _status(ui, repo, kwt, *pats, **opts) |
454 modified, added, removed, deleted, unknown, ignored, clean = status |
464 modified, added, removed, deleted, unknown, ignored, clean = status |
455 if modified or added or removed or deleted: |
465 if modified or added or removed or deleted: |
456 raise util.Abort(_('outstanding uncommitted changes in given files')) |
466 raise util.Abort(_('outstanding uncommitted changes in given files')) |
457 wlock = lock = None |
467 wlock = lock = None |
458 try: |
468 try: |
459 wlock = repo.wlock() |
469 wlock = repo.wlock() |
460 lock = repo.lock() |
470 lock = repo.lock() |
461 _overwrite(ui, repo, expand=expand, files=clean) |
471 kwt.overwrite(expand=expand, files=clean) |
462 finally: |
472 finally: |
463 del wlock, lock |
473 del wlock, lock |
464 |
474 |
465 |
475 |
466 def demo(ui, repo, *args, **opts): |
476 def demo(ui, repo, *args, **opts): |