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 |
82 |
83 from mercurial import commands, cmdutil, context, dispatch, filelog, revlog |
83 from mercurial import commands, cmdutil, dispatch, filelog, revlog |
84 from mercurial import patch, localrepo, templater, templatefilters, util |
84 from mercurial import patch, localrepo, templater, templatefilters, util |
85 from mercurial.hgweb import webcommands |
85 from mercurial.hgweb import webcommands |
86 from mercurial.node import nullid, hex |
86 from mercurial.node import nullid, hex |
87 from mercurial.i18n import _ |
87 from mercurial.i18n import _ |
88 import re, shutil, tempfile, time |
88 import re, shutil, tempfile, time |
142 False, '', False) |
142 False, '', False) |
143 |
143 |
144 def getnode(self, path, fnode): |
144 def getnode(self, path, fnode): |
145 '''Derives changenode from file path and filenode.''' |
145 '''Derives changenode from file path and filenode.''' |
146 # used by kwfilelog.read and kwexpand |
146 # used by kwfilelog.read and kwexpand |
147 c = context.filectx(self.repo, path, fileid=fnode) |
147 c = self.repo.filectx(path, fileid=fnode) |
148 return c.node() |
148 return c.node() |
149 |
149 |
150 def substitute(self, data, path, node, subfunc): |
150 def substitute(self, data, path, node, subfunc): |
151 '''Replaces keywords in data with expanded template.''' |
151 '''Replaces keywords in data with expanded template.''' |
152 def kwsub(mobj): |
152 def kwsub(mobj): |
163 if not self.restrict and self.matcher(path) and not util.binary(data): |
163 if not self.restrict and self.matcher(path) and not util.binary(data): |
164 changenode = self.getnode(path, node) |
164 changenode = self.getnode(path, node) |
165 return self.substitute(data, path, changenode, self.re_kw.sub) |
165 return self.substitute(data, path, changenode, self.re_kw.sub) |
166 return data |
166 return data |
167 |
167 |
168 def iskwfile(self, path, islink): |
168 def iskwfile(self, path, flagfunc): |
169 '''Returns true if path matches [keyword] pattern |
169 '''Returns true if path matches [keyword] pattern |
170 and is not a symbolic link. |
170 and is not a symbolic link. |
171 Caveat: localrepository._link fails on Windows.''' |
171 Caveat: localrepository._link fails on Windows.''' |
172 return self.matcher(path) and not islink(path) |
172 return self.matcher(path) and not 'l' in flagfunc(path) |
173 |
173 |
174 def overwrite(self, node, expand, files): |
174 def overwrite(self, node, expand, files): |
175 '''Overwrites selected files expanding/shrinking keywords.''' |
175 '''Overwrites selected files expanding/shrinking keywords.''' |
176 ctx = self.repo.changectx(node) |
|
177 mf = ctx.manifest() |
|
178 if node is not None: # commit |
176 if node is not None: # commit |
|
177 ctx = self.repo[node] |
|
178 mf = ctx.manifest() |
179 files = [f for f in ctx.files() if f in mf] |
179 files = [f for f in ctx.files() if f in mf] |
180 notify = self.ui.debug |
180 notify = self.ui.debug |
181 else: # kwexpand/kwshrink |
181 else: # kwexpand/kwshrink |
|
182 ctx = self.repo['.'] |
|
183 mf = ctx.manifest() |
182 notify = self.ui.note |
184 notify = self.ui.note |
183 candidates = [f for f in files if self.iskwfile(f, mf.linkf)] |
185 candidates = [f for f in files if self.iskwfile(f, ctx.flags)] |
184 if candidates: |
186 if candidates: |
185 self.restrict = True # do not expand when reading |
187 self.restrict = True # do not expand when reading |
186 candidates.sort() |
|
187 action = expand and 'expanding' or 'shrinking' |
188 action = expand and 'expanding' or 'shrinking' |
188 for f in candidates: |
189 for f in candidates: |
189 fp = self.repo.file(f) |
190 fp = self.repo.file(f) |
190 data = fp.read(mf[f]) |
191 data = fp.read(mf[f]) |
191 if util.binary(data): |
192 if util.binary(data): |
251 if self.renamed(node): |
252 if self.renamed(node): |
252 t2 = super(kwfilelog, self).read(node) |
253 t2 = super(kwfilelog, self).read(node) |
253 return t2 != text |
254 return t2 != text |
254 return revlog.revlog.cmp(self, node, text) |
255 return revlog.revlog.cmp(self, node, text) |
255 |
256 |
256 def _status(ui, repo, kwt, *pats, **opts): |
257 def _status(ui, repo, kwt, unknown, *pats, **opts): |
257 '''Bails out if [keyword] configuration is not active. |
258 '''Bails out if [keyword] configuration is not active. |
258 Returns status of working directory.''' |
259 Returns status of working directory.''' |
259 if kwt: |
260 if kwt: |
260 matcher = cmdutil.match(repo, pats, opts) |
261 matcher = cmdutil.match(repo, pats, opts) |
261 return repo.status(match=matcher, list_clean=True) |
262 return repo.status(match=matcher, unknown=unknown, clean=True) |
262 if ui.configitems('keyword'): |
263 if ui.configitems('keyword'): |
263 raise util.Abort(_('[keyword] patterns cannot match')) |
264 raise util.Abort(_('[keyword] patterns cannot match')) |
264 raise util.Abort(_('no [keyword] patterns configured')) |
265 raise util.Abort(_('no [keyword] patterns configured')) |
265 |
266 |
266 def _kwfwrite(ui, repo, expand, *pats, **opts): |
267 def _kwfwrite(ui, repo, expand, *pats, **opts): |
267 '''Selects files and passes them to kwtemplater.overwrite.''' |
268 '''Selects files and passes them to kwtemplater.overwrite.''' |
268 if repo.dirstate.parents()[1] != nullid: |
269 if repo.dirstate.parents()[1] != nullid: |
269 raise util.Abort(_('outstanding uncommitted merge')) |
270 raise util.Abort(_('outstanding uncommitted merge')) |
270 kwt = kwtools['templater'] |
271 kwt = kwtools['templater'] |
271 status = _status(ui, repo, kwt, *pats, **opts) |
272 status = _status(ui, repo, kwt, False, *pats, **opts) |
272 modified, added, removed, deleted, unknown, ignored, clean = status |
273 modified, added, removed, deleted = status[:4] |
273 if modified or added or removed or deleted: |
274 if modified or added or removed or deleted: |
274 raise util.Abort(_('outstanding uncommitted changes')) |
275 raise util.Abort(_('outstanding uncommitted changes')) |
275 wlock = lock = None |
276 wlock = lock = None |
276 try: |
277 try: |
277 wlock = repo.wlock() |
278 wlock = repo.wlock() |
278 lock = repo.lock() |
279 lock = repo.lock() |
279 kwt.overwrite(None, expand, clean) |
280 kwt.overwrite(None, expand, status[6]) |
280 finally: |
281 finally: |
281 del wlock, lock |
282 del wlock, lock |
282 |
283 |
283 |
284 |
284 def demo(ui, repo, *args, **opts): |
285 def demo(ui, repo, *args, **opts): |
378 Crosscheck which files in working directory are potential targets for |
379 Crosscheck which files in working directory are potential targets for |
379 keyword expansion. |
380 keyword expansion. |
380 That is, files matched by [keyword] config patterns but not symlinks. |
381 That is, files matched by [keyword] config patterns but not symlinks. |
381 ''' |
382 ''' |
382 kwt = kwtools['templater'] |
383 kwt = kwtools['templater'] |
383 status = _status(ui, repo, kwt, *pats, **opts) |
384 status = _status(ui, repo, kwt, opts.get('untracked'), *pats, **opts) |
384 modified, added, removed, deleted, unknown, ignored, clean = status |
385 modified, added, removed, deleted, unknown, ignored, clean = status |
385 files = modified + added + clean |
386 files = util.sort(modified + added + clean + unknown) |
386 if opts.get('untracked'): |
387 wctx = repo[None] |
387 files += unknown |
388 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)] |
388 files.sort() |
|
389 wctx = repo.workingctx() |
|
390 islink = lambda p: 'l' in wctx.fileflags(p) |
|
391 kwfiles = [f for f in files if kwt.iskwfile(f, islink)] |
|
392 cwd = pats and repo.getcwd() or '' |
389 cwd = pats and repo.getcwd() or '' |
393 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or () |
390 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or () |
394 if opts.get('all') or opts.get('ignore'): |
391 if opts.get('all') or opts.get('ignore'): |
395 kwfstats += (('I', [f for f in files if f not in kwfiles]),) |
392 kwfstats += (('I', [f for f in files if f not in kwfiles]),) |
396 for char, filenames in kwfstats: |
393 for char, filenames in kwfstats: |
511 fp=None, changes=None, opts=None): |
508 fp=None, changes=None, opts=None): |
512 '''Monkeypatch patch.diff to avoid expansion except when |
509 '''Monkeypatch patch.diff to avoid expansion except when |
513 comparing against working dir.''' |
510 comparing against working dir.''' |
514 if node2 is not None: |
511 if node2 is not None: |
515 kwt.matcher = util.never |
512 kwt.matcher = util.never |
516 elif node1 is not None and node1 != repo.dirstate.parents()[0]: |
513 elif node1 is not None and node1 != repo['.'].node(): |
517 kwt.restrict = True |
514 kwt.restrict = True |
518 patch_diff(repo, node1, node2, match, fp, changes, opts) |
515 patch_diff(repo, node1, node2, match, fp, changes, opts) |
519 |
516 |
520 def kwweb_annotate(web, req, tmpl): |
517 def kwweb_annotate(web, req, tmpl): |
521 '''Wraps webcommands.annotate turning off keyword expansion.''' |
518 '''Wraps webcommands.annotate turning off keyword expansion.''' |