12 not running a version control system. |
12 not running a version control system. |
13 |
13 |
14 For in-depth discussion refer to |
14 For in-depth discussion refer to |
15 <http://www.selenic.com/mercurial/wiki/index.cgi/KeywordPlan>. |
15 <http://www.selenic.com/mercurial/wiki/index.cgi/KeywordPlan>. |
16 |
16 |
17 You can either use the default cvs-like keywords or provide your |
17 Keywords are only expanded in local repositories and not logged by |
18 own in hgrc. |
18 Mercurial internally. The mechanism can be regarded as a convenience |
19 |
19 for the current user and may be turned off anytime. |
20 It is recommended to enable this extension on a per-repo basis only. |
20 |
21 You can still configure keywordmaps globally. |
21 Substitution takes place on every commit and update of the working |
22 |
22 repository. |
23 Expansions spanning more than one line are truncated to their first line. |
23 |
24 Incremental expansion (like CVS' $Log$) is not supported. |
24 Keyword expansion is based on Mercurial's changeset template mappings. |
|
25 The extension provides an additional UTC-date filter. |
|
26 |
|
27 The user has the choice either to create his own keywords and their |
|
28 expansions or to use the CVS-like default ones. |
25 |
29 |
26 Default $keywords$ and their $keyword: substition $ are: |
30 Default $keywords$ and their $keyword: substition $ are: |
27 Revision: changeset id |
31 Revision: changeset id |
28 Author: username |
32 Author: username |
29 Date: %Y/%m/%d %H:%M:%S [UTC] |
33 Date: %Y/%m/%d %H:%M:%S [UTC] |
30 RCSFile: basename,v |
34 RCSFile: basename,v |
31 Source: /path/to/basename,v |
35 Source: /path/to/basename,v |
32 Id: basename,v csetid %Y/%m/%d %H:%M:%S username |
36 Id: basename,v csetid %Y/%m/%d %H:%M:%S username |
33 Header: /path/to/basename,v csetid %Y/%m/%d %H:%M:%S username |
37 Header: /path/to/basename,v csetid %Y/%m/%d %H:%M:%S username |
34 |
38 |
|
39 Expansions spanning more than one line are truncated to their first line. |
|
40 Incremental expansion (like CVS' $Log$) is not supported. |
|
41 |
35 Simple setup in hgrc: |
42 Simple setup in hgrc: |
36 |
43 |
37 # enable extension |
44 # enable extension |
38 hgext.keyword = /full/path/to/script |
45 keyword = /full/path/to/keyword.py |
39 # or, if script in hgext folder: |
46 # or, if script in hgext folder: |
40 # hgext.keyword = |
47 # hgext.keyword = |
41 |
48 |
42 # filename patterns for expansion are configured in this section |
49 # filename patterns for expansion are configured in this section |
43 # files matching patterns with value 'ignore' are ignored |
50 # files matching patterns with value 'ignore' are ignored |
55 |
62 |
56 from mercurial.i18n import gettext as _ |
63 from mercurial.i18n import gettext as _ |
57 # above line for backwards compatibility; can be changed to |
64 # above line for backwards compatibility; can be changed to |
58 # from mercurial.i18n import _ |
65 # from mercurial.i18n import _ |
59 # some day |
66 # some day |
60 from mercurial import cmdutil, templater, util |
67 from mercurial import context, filelog, revlog |
61 from mercurial.node import * |
68 from mercurial import commands, cmdutil, templater, util |
|
69 from mercurial.node import bin |
62 import os.path, re, sys, time |
70 import os.path, re, sys, time |
63 |
71 |
64 deftemplates = { |
72 deftemplates = { |
65 'Revision': '{node|short}', |
73 'Revision': '{node|short}', |
66 'Author': '{author|user}', |
74 'Author': '{author|user}', |
73 |
81 |
74 def utcdate(date): |
82 def utcdate(date): |
75 '''Returns hgdate in cvs-like UTC format.''' |
83 '''Returns hgdate in cvs-like UTC format.''' |
76 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0])) |
84 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0])) |
77 |
85 |
|
86 def getmodulename(): |
|
87 '''Makes sure pretxncommit-hook can import keyword module |
|
88 regardless of where its located.''' |
|
89 for k, v in sys.modules.iteritems(): |
|
90 if v is None: |
|
91 continue |
|
92 if not hasattr(v, '__file__'): |
|
93 continue |
|
94 if v.__file__.startswith(__file__): |
|
95 return k |
|
96 else: |
|
97 sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) |
|
98 return os.path.splitext(os.path.basename(__file__))[0] |
|
99 |
78 def kwfmatches(ui, repo, files): |
100 def kwfmatches(ui, repo, files): |
79 '''Selects candidates for keyword substitution |
101 '''Selects candidates for keyword substitution |
80 configured in keyword section in hgrc.''' |
102 configured in keyword section in hgrc.''' |
81 inc = [pat for pat, opt in ui.configitems('keyword') if opt != 'ignore'] |
103 inc, exc = [], [] |
|
104 for pat, opt in ui.configitems('keyword'): |
|
105 if opt != 'ignore': |
|
106 inc.append(pat) |
|
107 else: |
|
108 exc.append(pat) |
82 if not inc: |
109 if not inc: |
83 ui.warn(_('keyword: no filename globs for expansion\n')) |
110 ui.debug(_('keyword: no filename globs for substitution\n')) |
84 return [] |
111 return [] |
85 exc = [pat for pat, opt in ui.configitems('keyword') if opt == 'ignore'] |
|
86 kwfmatcher = util.matcher(repo.root, inc=inc, exc=['.hg*']+exc)[1] |
112 kwfmatcher = util.matcher(repo.root, inc=inc, exc=['.hg*']+exc)[1] |
87 return [f for f in files if kwfmatcher(f)] |
113 return [f for f in files if kwfmatcher(f)] |
88 |
114 |
89 |
115 |
90 class kwtemplater(object): |
116 class kwtemplater(object): |
107 kw = mobj.group(1) |
133 kw = mobj.group(1) |
108 template = templater.parsestring(self.templates[kw], quoted=False) |
134 template = templater.parsestring(self.templates[kw], quoted=False) |
109 self.t.use_template(template) |
135 self.t.use_template(template) |
110 self.ui.pushbuffer() |
136 self.ui.pushbuffer() |
111 self.t.show(changenode=node, root=self.repo.root, file=path) |
137 self.t.show(changenode=node, root=self.repo.root, file=path) |
112 expansion = templater.firstline(self.ui.popbuffer()) |
138 kwsub = templater.firstline(self.ui.popbuffer()) |
113 return '$%s: %s $' % (kw, expansion) |
139 return '$%s: %s $' % (kw, kwsub) |
114 |
140 |
115 |
141 |
116 def reposetup(ui, repo): |
142 def reposetup(ui, repo): |
117 '''Sets up repo, and filelog especially, as kwrepo and kwfilelog |
143 '''Sets up repo, and filelog especially, as kwrepo and kwfilelog |
118 for keyword substitution. This is done for local repos only.''' |
144 for keyword substitution. This is done for local repos only.''' |
119 |
145 |
120 from mercurial import context, filelog, revlog |
|
121 if not repo.local(): |
146 if not repo.local(): |
122 return |
147 return |
|
148 |
123 |
149 |
124 class kwrepo(repo.__class__): |
150 class kwrepo(repo.__class__): |
125 def file(self, f): |
151 def file(self, f): |
126 if f[0] == '/': |
152 if f[0] == '/': |
127 f = f[1:] |
153 f = f[1:] |
170 text = self.kwt.re_kw.sub(r'$\1$', text) |
196 text = self.kwt.re_kw.sub(r'$\1$', text) |
171 return super(kwfilelog, self).cmp(node, text) |
197 return super(kwfilelog, self).cmp(node, text) |
172 |
198 |
173 filelog.filelog = kwfilelog |
199 filelog.filelog = kwfilelog |
174 repo.__class__ = kwrepo |
200 repo.__class__ = kwrepo |
175 # make pretxncommit hook import kwmodule regardless of where it's located |
201 # configure pretxncommit hook |
176 for k, v in sys.modules.iteritems(): |
202 ui.setconfig('hooks', 'pretxncommit.keyword', |
177 if v is None: |
203 'python:%s.pretxnkw' % getmodulename()) |
178 continue |
|
179 if not hasattr(v, '__file__'): |
|
180 continue |
|
181 if v.__file__.startswith(__file__): |
|
182 mod = k |
|
183 break |
|
184 else: |
|
185 sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) |
|
186 mod = os.path.splitext(os.path.basename(__file__))[0] |
|
187 ui.setconfig('hooks', 'pretxncommit.keyword', 'python:%s.pretxnkw' % mod) |
|
188 del mod |
|
189 |
204 |
190 |
205 |
191 def pretxnkw(ui, repo, hooktype, **args): |
206 def pretxnkw(ui, repo, hooktype, **args): |
192 '''pretxncommit hook that collects candidates for keyword expansion |
207 '''pretxncommit hook that collects candidates for keyword expansion |
193 on commit and expands keywords in working dir.''' |
208 on commit and expands keywords in working dir.''' |
194 from mercurial import commands |
|
195 |
209 |
196 cmd, sysargs, globalopts, cmdopts = commands.parse(ui, sys.argv[1:])[1:] |
210 cmd, sysargs, globalopts, cmdopts = commands.parse(ui, sys.argv[1:])[1:] |
197 if repr(cmd).split()[1] in ('tag', 'import_'): |
211 if repr(cmd).split()[1] in ('tag', 'import_'): |
198 return |
212 return |
199 |
213 |