hgkw/keyword.py
changeset 407 60c993ce6acb
parent 406 2069334ec1d0
child 408 33e5b8a51e47
equal deleted inserted replaced
406:2069334ec1d0 407:60c993ce6acb
   112 
   112 
   113 def _kwpatchfile_init(self, ui, fname, missing=False):
   113 def _kwpatchfile_init(self, ui, fname, missing=False):
   114     '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
   114     '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
   115     rejects or conflicts due to expanded keywords in working dir.'''
   115     rejects or conflicts due to expanded keywords in working dir.'''
   116     _patchfile_init(self, ui, fname, missing=missing)
   116     _patchfile_init(self, ui, fname, missing=missing)
   117     if _kwtemplater.matcher(self.fname):
   117     # shrink keywords read from working dir
   118         # shrink keywords read from working dir
   118     self.lines = _kwtemplater.shrinklines(self.fname, self.lines)
   119         kwshrunk = _kwtemplater.shrink(''.join(self.lines))
       
   120         self.lines = kwshrunk.splitlines(True)
       
   121 
   119 
   122 def _kw_diff(repo, node1=None, node2=None, files=None, match=util.always,
   120 def _kw_diff(repo, node1=None, node2=None, files=None, match=util.always,
   123              fp=None, changes=None, opts=None):
   121              fp=None, changes=None, opts=None):
   124     # only expand if comparing against working dir
   122     # only expand if comparing against working dir
   125     if node2 is not None:
   123     if node2 is not None:
   162         'Source': '{root}/{file},v',
   160         'Source': '{root}/{file},v',
   163         'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
   161         'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
   164         'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
   162         'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
   165     }
   163     }
   166 
   164 
   167     def __init__(self, ui, repo, inc, exc, hgcmd):
   165     def __init__(self, ui, repo, inc, exc):
   168         self.ui = ui
   166         self.ui = ui
   169         self.repo = repo
   167         self.repo = repo
   170         self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
   168         self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
   171         self.restrict = hgcmd in restricted.split()
   169         self.restrict = _cmd in restricted.split()
   172         self.commitnode = None
       
   173         self.path = ''
       
   174 
   170 
   175         kwmaps = self.ui.configitems('keywordmaps')
   171         kwmaps = self.ui.configitems('keywordmaps')
   176         if kwmaps: # override default templates
   172         if kwmaps: # override default templates
   177             kwmaps = [(k, templater.parsestring(v, quoted=False))
   173             kwmaps = [(k, templater.parsestring(v, quoted=False))
   178                       for (k, v) in kwmaps]
   174                       for (k, v) in kwmaps]
   183 
   179 
   184         templatefilters.filters['utcdate'] = utcdate
   180         templatefilters.filters['utcdate'] = utcdate
   185         self.ct = cmdutil.changeset_templater(self.ui, self.repo,
   181         self.ct = cmdutil.changeset_templater(self.ui, self.repo,
   186                                               False, '', False)
   182                                               False, '', False)
   187 
   183 
   188     def substitute(self, node, data, subfunc):
   184     def getnode(self, path, fnode):
   189         '''Obtains file's changenode if commit node not given,
   185         '''Derives changenode from file context.'''
   190         and calls given substitution function.'''
   186         c = context.filectx(self.repo, path, fileid=fnode)
   191         if self.commitnode:
   187         return c.node()
   192             fnode = self.commitnode
   188 
   193         else:
   189     def substitute(self, data, path, node, subfunc):
   194             c = context.filectx(self.repo, self.path, fileid=node)
   190         '''Replaces keywords in data with expanded template.'''
   195             fnode = c.node()
       
   196 
       
   197         def kwsub(mobj):
   191         def kwsub(mobj):
   198             '''Substitutes keyword using corresponding template.'''
       
   199             kw = mobj.group(1)
   192             kw = mobj.group(1)
   200             self.ct.use_template(self.templates[kw])
   193             self.ct.use_template(self.templates[kw])
   201             self.ui.pushbuffer()
   194             self.ui.pushbuffer()
   202             self.ct.show(changenode=fnode, root=self.repo.root, file=self.path)
   195             self.ct.show(changenode=node, root=self.repo.root, file=path)
   203             ekw = templatefilters.firstline(self.ui.popbuffer())
   196             ekw = templatefilters.firstline(self.ui.popbuffer())
   204             return '$%s: %s $' % (kw, ekw)
   197             return '$%s: %s $' % (kw, ekw)
   205 
       
   206         return subfunc(kwsub, data)
   198         return subfunc(kwsub, data)
   207 
   199 
   208     def expand(self, node, data):
   200     def expand(self, path, node, data):
   209         '''Returns data with keywords expanded.'''
   201         '''Returns data with keywords expanded.'''
   210         if self.restrict or util.binary(data):
   202         if not self.restrict and self.matcher(path) and not util.binary(data):
   211             return data
   203             changenode = self.getnode(path, node)
   212         return self.substitute(node, data, self.re_kw.sub)
   204             return self.substitute(data, path, changenode, self.re_kw.sub)
   213 
   205         return data
   214     def process(self, node, data, expand):
   206 
   215         '''Returns a tuple: data, count.
   207     def iskwfile(self, path, islink):
   216         Count is number of keywords/keyword substitutions,
   208         '''Returns true if path matches [keyword] pattern
   217         telling caller whether to act on file containing data.'''
   209         and is not a symbolic link.
   218         if util.binary(data):
   210         Caveat: localrepository._link fails on Windows.'''
   219             return data, None
   211         return self.matcher(path) and not islink(path)
   220         if expand:
   212 
   221             return self.substitute(node, data, self.re_kw.subn)
   213     def overwrite(self, node=None, expand=True, files=None):
   222         return data, self.re_kw.search(data)
   214         '''Overwrites selected files expanding/shrinking keywords.'''
   223 
   215         ctx = self.repo.changectx(node)
   224     def shrink(self, text):
   216         mf = ctx.manifest()
       
   217         if node is not None:     # commit
       
   218             files = [f for f in ctx.files() if f in mf]
       
   219             notify = self.ui.debug
       
   220         else:                    # kwexpand/kwshrink
       
   221             notify = self.ui.note
       
   222         candidates = [f for f in files if self.iskwfile(f, mf.linkf)]
       
   223         if candidates:
       
   224             self.restrict = True # do not expand when reading
       
   225             candidates.sort()
       
   226             action = expand and 'expanding' or 'shrinking'
       
   227             for f in candidates:
       
   228                 fp = self.repo.file(f)
       
   229                 data = fp.read(mf[f])
       
   230                 if util.binary(data):
       
   231                     continue
       
   232                 if expand:
       
   233                     changenode = node or self.getnode(f, mf[f])
       
   234                     data, found = self.substitute(data, f, changenode,
       
   235                                                   self.re_kw.subn)
       
   236                 else:
       
   237                     found = self.re_kw.search(data)
       
   238                 if found:
       
   239                     notify(_('overwriting %s %s keywords\n') % (f, action))
       
   240                     self.repo.wwrite(f, data, mf.flags(f))
       
   241                     self.repo.dirstate.normal(f)
       
   242             self.restrict = False
       
   243 
       
   244     def shrinktext(self, text):
       
   245         '''Unconditionally removes all keyword substitutions from text.'''
       
   246         return self.re_kw.sub(r'$\1$', text)
       
   247 
       
   248     def shrink(self, fname, text):
   225         '''Returns text with all keyword substitutions removed.'''
   249         '''Returns text with all keyword substitutions removed.'''
   226         if util.binary(text):
   250         if self.matcher(fname) and not util.binary(text):
   227             return text
   251             return self.shrinktext(text)
   228         return self.re_kw.sub(r'$\1$', text)
   252         return text
       
   253 
       
   254     def shrinklines(self, fname, lines):
       
   255         '''Returns lines with keyword substitutions removed.'''
       
   256         if self.matcher(fname):
       
   257             text = ''.join(lines)
       
   258             if not util.binary(text):
       
   259                 return self.shrinktext(text).splitlines(True)
       
   260         return lines
       
   261 
       
   262     def wread(self, fname, data):
       
   263         '''If in restricted mode returns data read from wdir with
       
   264         keyword substitutions removed.'''
       
   265         return self.restrict and self.shrink(fname, data) or data
   229 
   266 
   230 class kwfilelog(filelog.filelog):
   267 class kwfilelog(filelog.filelog):
   231     '''
   268     '''
   232     Subclass of filelog to hook into its read, add, cmp methods.
   269     Subclass of filelog to hook into its read, add, cmp methods.
   233     Keywords are "stored" unexpanded, and processed on reading.
   270     Keywords are "stored" unexpanded, and processed on reading.
   234     '''
   271     '''
   235     def __init__(self, opener, path):
   272     def __init__(self, opener, path):
   236         super(kwfilelog, self).__init__(opener, path)
   273         super(kwfilelog, self).__init__(opener, path)
   237         _kwtemplater.path = path
   274         self.path = path
   238 
       
   239     def kwctread(self, node, expand):
       
   240         '''Reads expanding and counting keywords, called from _overwrite.'''
       
   241         data = super(kwfilelog, self).read(node)
       
   242         return _kwtemplater.process(node, data, expand)
       
   243 
   275 
   244     def read(self, node):
   276     def read(self, node):
   245         '''Expands keywords when reading filelog.'''
   277         '''Expands keywords when reading filelog.'''
   246         data = super(kwfilelog, self).read(node)
   278         data = super(kwfilelog, self).read(node)
   247         return _kwtemplater.expand(node, data)
   279         return _kwtemplater.expand(self.path, node, data)
   248 
   280 
   249     def add(self, text, meta, tr, link, p1=None, p2=None):
   281     def add(self, text, meta, tr, link, p1=None, p2=None):
   250         '''Removes keyword substitutions when adding to filelog.'''
   282         '''Removes keyword substitutions when adding to filelog.'''
   251         text = _kwtemplater.shrink(text)
   283         text = _kwtemplater.shrink(self.path, text)
   252         return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2)
   284         return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2)
   253 
   285 
   254     def cmp(self, node, text):
   286     def cmp(self, node, text):
   255         '''Removes keyword substitutions for comparison.'''
   287         '''Removes keyword substitutions for comparison.'''
   256         text = _kwtemplater.shrink(text)
   288         text = _kwtemplater.shrink(self.path, text)
   257         if self.renamed(node):
   289         if self.renamed(node):
   258             t2 = super(kwfilelog, self).read(node)
   290             t2 = super(kwfilelog, self).read(node)
   259             return t2 != text
   291             return t2 != text
   260         return revlog.revlog.cmp(self, node, text)
   292         return revlog.revlog.cmp(self, node, text)
   261 
       
   262 def _iskwfile(f, link):
       
   263     return not link(f) and _kwtemplater.matcher(f)
       
   264 
   293 
   265 def _status(ui, repo, *pats, **opts):
   294 def _status(ui, repo, *pats, **opts):
   266     '''Bails out if [keyword] configuration is not active.
   295     '''Bails out if [keyword] configuration is not active.
   267     Returns status of working directory.'''
   296     Returns status of working directory.'''
   268     if _kwtemplater:
   297     if _kwtemplater:
   270         return repo.status(files=files, match=match, list_clean=True)
   299         return repo.status(files=files, match=match, list_clean=True)
   271     if ui.configitems('keyword'):
   300     if ui.configitems('keyword'):
   272         raise util.Abort(_('[keyword] patterns cannot match'))
   301         raise util.Abort(_('[keyword] patterns cannot match'))
   273     raise util.Abort(_('no [keyword] patterns configured'))
   302     raise util.Abort(_('no [keyword] patterns configured'))
   274 
   303 
   275 def _overwrite(ui, repo, node=None, expand=True, files=None):
       
   276     '''Overwrites selected files expanding/shrinking keywords.'''
       
   277     ctx = repo.changectx(node)
       
   278     mf = ctx.manifest()
       
   279     if node is not None:   # commit
       
   280         _kwtemplater.commitnode = node
       
   281         files = [f for f in ctx.files() if f in mf]
       
   282         notify = ui.debug
       
   283     else:                  # kwexpand/kwshrink
       
   284         notify = ui.note
       
   285     candidates = [f for f in files if _iskwfile(f, mf.linkf)]
       
   286     if candidates:
       
   287         candidates.sort()
       
   288         action = expand and 'expanding' or 'shrinking'
       
   289         for f in candidates:
       
   290             fp = repo.file(f, kwmatch=True)
       
   291             data, kwfound = fp.kwctread(mf[f], expand)
       
   292             if kwfound:
       
   293                 notify(_('overwriting %s %s keywords\n') % (f, action))
       
   294                 repo.wwrite(f, data, mf.flags(f))
       
   295                 repo.dirstate.normal(f)
       
   296 
       
   297 def _kwfwrite(ui, repo, expand, *pats, **opts):
   304 def _kwfwrite(ui, repo, expand, *pats, **opts):
   298     '''Selects files and passes them to _overwrite.'''
   305     '''Selects files and passes them to kwtemplater.overwrite.'''
   299     status = _status(ui, repo, *pats, **opts)
   306     status = _status(ui, repo, *pats, **opts)
   300     modified, added, removed, deleted, unknown, ignored, clean = status
   307     modified, added, removed, deleted, unknown, ignored, clean = status
   301     if modified or added or removed or deleted:
   308     if modified or added or removed or deleted:
   302         raise util.Abort(_('outstanding uncommitted changes in given files'))
   309         raise util.Abort(_('outstanding uncommitted changes in given files'))
   303     wlock = lock = None
   310     wlock = lock = None
   304     try:
   311     try:
   305         wlock = repo.wlock()
   312         wlock = repo.wlock()
   306         lock = repo.lock()
   313         lock = repo.lock()
   307         _overwrite(ui, repo, expand=expand, files=clean)
   314         _kwtemplater.overwrite(expand=expand, files=clean)
   308     finally:
   315     finally:
   309         del wlock, lock
   316         del wlock, lock
   310 
   317 
   311 
   318 
   312 def demo(ui, repo, *args, **opts):
   319 def demo(ui, repo, *args, **opts):
   412     if opts.get('untracked'):
   419     if opts.get('untracked'):
   413         files += unknown
   420         files += unknown
   414     files.sort()
   421     files.sort()
   415     wctx = repo.workingctx()
   422     wctx = repo.workingctx()
   416     islink = lambda p: 'l' in wctx.fileflags(p)
   423     islink = lambda p: 'l' in wctx.fileflags(p)
   417     kwfiles = [f for f in files if _iskwfile(f, islink)]
   424     kwfiles = [f for f in files if _kwtemplater.iskwfile(f, islink)]
   418     cwd = pats and repo.getcwd() or ''
   425     cwd = pats and repo.getcwd() or ''
   419     kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
   426     kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
   420     if opts.get('all') or opts.get('ignore'):
   427     if opts.get('all') or opts.get('ignore'):
   421         kwfstats += (('I', [f for f in files if f not in kwfiles]),)
   428         kwfstats += (('I', [f for f in files if f not in kwfiles]),)
   422     for char, filenames in kwfstats:
   429     for char, filenames in kwfstats:
   462         else:
   469         else:
   463             exc.append(pat)
   470             exc.append(pat)
   464     if not inc:
   471     if not inc:
   465         return
   472         return
   466 
   473 
   467     _kwtemplater = kwtemplater(ui, repo, inc, exc, _cmd)
   474     _kwtemplater = kwtemplater(ui, repo, inc, exc)
   468 
   475 
   469     class kwrepo(repo.__class__):
   476     class kwrepo(repo.__class__):
   470         def file(self, f, kwmatch=False):
   477         def file(self, f):
   471             if f[0] == '/':
   478             if f[0] == '/':
   472                 f = f[1:]
   479                 f = f[1:]
   473             if kwmatch or _kwtemplater.matcher(f):
   480             return kwfilelog(self.sopener, f)
   474                 return kwfilelog(self.sopener, f)
       
   475             return filelog.filelog(self.sopener, f)
       
   476 
   481 
   477         def wread(self, filename):
   482         def wread(self, filename):
   478             data = super(kwrepo, self).wread(filename)
   483             data = super(kwrepo, self).wread(filename)
   479             if _kwtemplater.restrict and _kwtemplater.matcher(filename):
   484             return _kwtemplater.wread(filename, data)
   480                 return _kwtemplater.shrink(data)
       
   481             return data
       
   482 
   485 
   483         def commit(self, files=None, text='', user=None, date=None,
   486         def commit(self, files=None, text='', user=None, date=None,
   484                    match=util.always, force=False, force_editor=False,
   487                    match=util.always, force=False, force_editor=False,
   485                    p1=None, p2=None, extra={}, empty_ok=False):
   488                    p1=None, p2=None, extra={}, empty_ok=False):
   486             wlock = lock = None
   489             wlock = lock = None
   515 
   518 
   516                 # restore commit hooks
   519                 # restore commit hooks
   517                 for name, cmd in commithooks.iteritems():
   520                 for name, cmd in commithooks.iteritems():
   518                     ui.setconfig('hooks', name, cmd)
   521                     ui.setconfig('hooks', name, cmd)
   519                 if node is not None:
   522                 if node is not None:
   520                     _overwrite(ui, self, node=node)
   523                     _kwtemplater.overwrite(node=node)
   521                     repo.hook('commit', node=node, parent1=_p1, parent2=_p2)
   524                     repo.hook('commit', node=node, parent1=_p1, parent2=_p2)
   522                 return node
   525                 return node
   523             finally:
   526             finally:
   524                 del wlock, lock
   527                 del wlock, lock
   525 
   528