8 There are many good reasons why this is not needed in a distributed |
8 There are many good reasons why this is not needed in a distributed |
9 SCM, still it may be useful in very small projects based on single |
9 SCM, still it may be useful in very small projects based on single |
10 files (like LaTeX packages), that are mostly addressed to an audience |
10 files (like LaTeX packages), that are mostly addressed to an audience |
11 not running a version control system. |
11 not running a version control system. |
12 |
12 |
13 Supported keywords are (changeset 000000000000): |
13 Supported $keywords$ and their $keyword: substition $ are: |
14 $Revision: 000000000000 $ |
14 Revision: changeset id |
15 $Author: Your Name <address@example.com> $ |
15 Author: full username |
16 $Date: %a %b %d %H:%M:%S %Y %z $ |
16 Date: %a %b %d %H:%M:%S %Y %z $ |
17 $RCSFile: basename,v $ |
17 RCSFile: basename,v |
18 $Source: /path/to/basename,v $ |
18 Source: /path/to/basename,v |
19 $Id: basename,v 000000000000 %Y-%m-%d %H:%M:%S %z shortname $ |
19 Id: basename,v csetid %Y-%m-%d %H:%M:%S %z shortname |
20 $Header: /path/to/basename,v 000000000000 %Y-%m-%d %H:%M:%S %z shortname $ |
20 Header: /path/to/basename,v csetid %Y-%m-%d %H:%M:%S %z shortname |
21 |
21 |
22 The extension, according to its hackish nature, is a hybrid and consists |
22 Install: |
23 actually in 2 parts: |
23 Copy keyword.py in hgext folder. |
24 |
|
25 1. pure extension code (reposetup) that is triggered on checkout and |
|
26 logging of changes. |
|
27 2. a pretxncommit hook (hgrc (5)) that expands keywords immediately |
|
28 at commit time in the working directory. |
|
29 |
24 |
30 Simple setup in hgrc: |
25 Simple setup in hgrc: |
31 |
26 |
32 # enable extension |
27 # enable extension |
33 hgext.keyword = |
28 hgext.keyword = |
34 |
29 |
35 # filename patterns for expansion are configured in this section |
30 # filename patterns for expansion are configured in this section |
36 [keyword] |
31 [keyword] |
37 *.sty = expand |
32 **.py = expand |
38 ... |
33 ... |
39 |
|
40 # set up pretxncommit hook |
|
41 [hooks] |
|
42 pretxncommit = |
|
43 pretxncommit.keyword = python:hgext.keyword.pretxnkw |
|
44 ''' |
34 ''' |
45 |
35 |
46 from mercurial.i18n import _ |
|
47 from mercurial import context, util |
36 from mercurial import context, util |
48 import os.path, re |
37 import os.path, re |
49 |
38 |
50 |
39 |
51 re_kw = re.compile( |
40 re_kw = re.compile( |
54 |
43 |
55 def kwexpand(matchobj, repo, path, changeid=None, fileid=None, filelog=None): |
44 def kwexpand(matchobj, repo, path, changeid=None, fileid=None, filelog=None): |
56 '''Called by kwfilelog.read and pretxnkw. |
45 '''Called by kwfilelog.read and pretxnkw. |
57 Sets supported keywords as local variables and evaluates them to |
46 Sets supported keywords as local variables and evaluates them to |
58 their expansion if matchobj is equal to string representation.''' |
47 their expansion if matchobj is equal to string representation.''' |
59 |
|
60 c = context.filectx(repo, path, |
48 c = context.filectx(repo, path, |
61 changeid=changeid, fileid=fileid, filelog=filelog) |
49 changeid=changeid, fileid=fileid, filelog=filelog) |
62 date = c.date() |
50 date = c.date() |
63 |
|
64 Revision = c.changectx() |
51 Revision = c.changectx() |
65 Author = c.user() |
52 Author = c.user() |
66 RCSFile = os.path.basename(path)+',v' |
53 RCSFile = os.path.basename(path)+',v' |
67 Source = repo.wjoin(path)+',v' |
54 Source = repo.wjoin(path)+',v' |
68 Date = util.datestr(date=date) |
55 Date = util.datestr(date=date) |
69 revdateauth = '%s %s %s' % (Revision, |
56 revdateauth = '%s %s %s' % (Revision, |
70 util.datestr(date=date, format=util.defaultdateformats[0]), |
57 util.datestr(date=date, format=util.defaultdateformats[0]), |
71 util.shortuser(Author)) |
58 util.shortuser(Author)) |
72 Header = '%s %s' % (Source, revdateauth) |
59 Header = '%s %s' % (Source, revdateauth) |
73 Id = '%s %s' % (RCSFile, revdateauth) |
60 Id = '%s %s' % (RCSFile, revdateauth) |
74 |
|
75 return '$%s: %s $' % (matchobj.group(1), eval(matchobj.group(1))) |
61 return '$%s: %s $' % (matchobj.group(1), eval(matchobj.group(1))) |
76 |
62 |
77 def kwfmatchers(ui, repo): |
63 def kwfmatches(ui, repo, files): |
78 '''Returns filename matchers from ui keyword section.''' |
64 '''Selects candidates for keyword substitution |
79 return [util.matcher(repo.root, '', [pat], [], [])[1] |
65 configured in keyword section in hgrc.''' |
|
66 files = [f for f in files if not f.startswith('.hg')] |
|
67 if not files: |
|
68 return [] |
|
69 candidates = [] |
|
70 fmatchers = [util.matcher(repo.root, '', [pat], [], [])[1] |
80 for pat, opt in ui.configitems('keyword') |
71 for pat, opt in ui.configitems('keyword') |
81 if opt == 'expand'] |
72 if opt == 'expand'] |
|
73 for f in files: |
|
74 for mf in fmatchers: |
|
75 if mf(f): |
|
76 candidates.append(f) |
|
77 break |
|
78 return candidates |
82 |
79 |
83 |
80 |
84 def reposetup(ui, repo): |
81 def reposetup(ui, repo): |
85 from mercurial import filelog, revlog |
82 from mercurial import filelog, revlog |
86 |
83 |
92 if f[0] == '/': |
89 if f[0] == '/': |
93 f = f[1:] |
90 f = f[1:] |
94 return filelog.filelog(self.sopener, f, self, self.revlogversion) |
91 return filelog.filelog(self.sopener, f, self, self.revlogversion) |
95 |
92 |
96 class kwfilelog(filelog.filelog): |
93 class kwfilelog(filelog.filelog): |
|
94 ''' |
|
95 Superclass over filelog to customize it's read, add, cmp methods. |
|
96 Keywords are "stored" unexpanded, and expanded on reading. |
|
97 ''' |
97 def __init__(self, opener, path, repo, |
98 def __init__(self, opener, path, repo, |
98 defversion=revlog.REVLOG_DEFAULT_VERSION): |
99 defversion=revlog.REVLOG_DEFAULT_VERSION): |
99 super(kwfilelog, self).__init__(opener, path, defversion) |
100 super(kwfilelog, self).__init__(opener, path, defversion) |
100 self._repo = repo |
101 self._repo = repo |
101 self._path = path |
102 self._path = path |
102 |
103 |
|
104 def iskwcandidate(self, data): |
|
105 '''Decides whether to act on keywords.''' |
|
106 return (kwfmatches(ui, self._repo, [self._path]) |
|
107 and not util.binary(data)) |
|
108 |
103 def read(self, node): |
109 def read(self, node): |
|
110 '''Substitutes keywords when reading filelog.''' |
104 data = super(kwfilelog, self).read(node) |
111 data = super(kwfilelog, self).read(node) |
105 if not self._path.startswith('.hg') and not util.binary(data): |
112 if self.iskwcandidate(data): |
106 for mf in kwfmatchers(ui, self._repo): |
113 return re_kw.sub(lambda m: |
107 if mf(self._path): |
114 kwexpand(m, self._repo, self._path, |
108 ui.debug(_('expanding keywords in %s\n' % self._path)) |
115 fileid=node, filelog=self), data) |
109 return re_kw.sub(lambda m: |
|
110 kwexpand(m, self._repo, self._path, |
|
111 fileid=node, filelog=self), |
|
112 data) |
|
113 return data |
116 return data |
114 |
117 |
115 def size(self, rev): |
118 def add(self, text, meta, tr, link, p1=None, p2=None): |
116 '''Overrides filelog's size() to use kwfilelog.read().''' |
119 '''Removes keyword substitutions when adding to filelog.''' |
117 node = revlog.node(self, rev) |
120 if self.iskwcandidate(text): |
118 if super(kwfilelog, self).renamed(node): |
121 text = re_kw.sub(r'$\1$', text) |
119 return len(self.read(node)) |
122 return super(kwfilelog, self).add(text, |
120 return revlog.size(self, rev) |
123 meta, tr, link, p1=None, p2=None) |
121 |
124 |
122 def cmp(self, node, text): |
125 def cmp(self, node, text): |
123 '''Overrides filelog's cmp() to use kwfilelog.read().''' |
126 '''Removes keyword substitutions for comparison.''' |
124 if super(kwfilelog, self).renamed(node): |
127 if self.iskwcandidate(text): |
125 t2 = self.read(node) |
128 text = re_kw.sub(r'$\1$', text) |
126 return t2 != text |
129 return super(kwfilelog, self).cmp(node, text) |
127 |
130 |
128 filelog.filelog = kwfilelog |
131 filelog.filelog = kwfilelog |
129 repo.__class__ = kwrepo |
132 repo.__class__ = kwrepo |
|
133 ui.setconfig('hooks', |
|
134 'pretxncommit.keyword', 'python:hgext.keyword.pretxnkw') |
130 |
135 |
131 |
136 |
132 def pretxnkw(ui, repo, hooktype, **args): |
137 def pretxnkw(ui, repo, hooktype, **args): |
133 '''pretxncommit hook that collects candidates for keyword expansion |
138 '''pretxncommit hook that collects candidates for keyword expansion |
134 on commit and expands keywords in working dir.''' |
139 on commit and expands keywords in working dir.''' |
|
140 from mercurial.i18n import gettext as _ |
135 from mercurial import cmdutil, commands |
141 from mercurial import cmdutil, commands |
136 import sys |
142 import sys |
137 |
143 |
138 if hooktype != 'pretxncommit': |
144 if hooktype != 'pretxncommit': |
139 return True |
145 return True |
142 if repr(cmd).split()[1] in ('tag', 'import_'): |
148 if repr(cmd).split()[1] in ('tag', 'import_'): |
143 return False |
149 return False |
144 |
150 |
145 files, match, anypats = cmdutil.matchpats(repo, sysargs, cmdopts) |
151 files, match, anypats = cmdutil.matchpats(repo, sysargs, cmdopts) |
146 modified, added = repo.status(files=files, match=match)[:2] |
152 modified, added = repo.status(files=files, match=match)[:2] |
147 candidates = [f for f in modified + added if not f.startswith('.hg')] |
|
148 if not candidates: |
|
149 return False |
|
150 |
153 |
151 fmatchers = kwfmatchers(ui, repo) |
154 for f in kwfmatches(ui, repo, modified+added): |
152 for f in candidates: |
155 data = repo.wfile(f).read() |
153 for mf in fmatchers: |
156 if not util.binary(data): |
154 if mf(f): |
157 data, kwct = re_kw.subn(lambda m: |
155 data = repo.wfile(f).read() |
158 kwexpand(m, repo, f, changeid=args['node']), |
156 if not util.binary(data): |
159 data) |
157 data, kwct = re_kw.subn(lambda m: |
160 if kwct: |
158 kwexpand(m, repo, f, changeid=args['node']), |
161 ui.debug(_('overwriting %s expanding keywords\n' % f)) |
159 data) |
162 repo.wfile(f, 'w').write(data) |
160 if kwct: |
|
161 ui.debug(_('overwriting %s expanding keywords\n' % f)) |
|
162 repo.wfile(f, 'w').write(data) |
|
163 break |
|