55 |
55 |
56 from mercurial.i18n import gettext as _ |
56 from mercurial.i18n import gettext as _ |
57 # above line for backwards compatibility; can be changed to |
57 # above line for backwards compatibility; can be changed to |
58 # from mercurial.i18n import _ |
58 # from mercurial.i18n import _ |
59 # some day |
59 # some day |
60 from mercurial import cmdutil, templater, util |
60 from mercurial import context, filelog, revlog |
|
61 from mercurial import commands, cmdutil, templater, util |
61 from mercurial.node import * |
62 from mercurial.node import * |
62 import os.path, re, sys, time |
63 import os.path, re, sys, time |
63 |
64 |
64 deftemplates = { |
65 deftemplates = { |
65 'Revision': '{node|short}', |
66 'Revision': '{node|short}', |
73 |
74 |
74 def utcdate(date): |
75 def utcdate(date): |
75 '''Returns hgdate in cvs-like UTC format.''' |
76 '''Returns hgdate in cvs-like UTC format.''' |
76 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0])) |
77 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0])) |
77 |
78 |
78 def kwfmatches(ui, repo, files): |
79 def getkwconfig(ui, repo): |
79 '''Selects candidates for keyword substitution |
|
80 configured in keyword section in hgrc.''' |
|
81 inc = [pat for pat, opt in ui.configitems('keyword') if opt != 'ignore'] |
80 inc = [pat for pat, opt in ui.configitems('keyword') if opt != 'ignore'] |
82 if not inc: |
81 if not inc: |
83 ui.warn(_('keyword: no filename globs for expansion\n')) |
82 ui.warn(_('keyword: no filename globs for substitution\n')) |
84 return [] |
83 return None, None |
85 exc = [pat for pat, opt in ui.configitems('keyword') if opt == 'ignore'] |
84 exc = [pat for pat, opt in ui.configitems('keyword') if opt == 'ignore'] |
86 kwfmatcher = util.matcher(repo.root, inc=inc, exc=['.hg*']+exc)[1] |
85 return inc, exc |
87 return [f for f in files if kwfmatcher(f)] |
|
88 |
86 |
89 |
87 |
90 class kwtemplater(object): |
88 class kwtemplater(object): |
91 ''' |
89 ''' |
92 Sets up keyword templates, corresponding keyword regex, and |
90 Sets up keyword templates, corresponding keyword regex, and |
93 provides keyword expansion function. |
91 provides keyword expansion function. |
|
92 |
|
93 If a repo is configured for keyword substitution, this class |
|
94 will be set as an (appendix) attribute to the repo. |
94 ''' |
95 ''' |
95 def __init__(self, ui, repo): |
96 def __init__(self, ui, repo): |
96 self.ui = ui |
97 self.ui = ui |
97 self.repo = repo |
98 self.repo = repo |
98 self.templates = dict(ui.configitems('keywordmaps')) or deftemplates |
99 self.templates = dict(ui.configitems('keywordmaps')) or deftemplates |
99 self.re_kw = re.compile(r'\$(%s)[^$]*?\$' % |
100 self.re_kw = re.compile(r'\$(%s)[^$]*?\$' % |
100 '|'.join(re.escape(k) for k in self.templates.keys())) |
101 '|'.join(re.escape(k) for k in self.templates.keys())) |
101 templater.common_filters['utcdate'] = utcdate |
102 templater.common_filters['utcdate'] = utcdate |
102 self.t = cmdutil.changeset_templater(ui, repo, False, '', False) |
103 self.t = cmdutil.changeset_templater(ui, repo, False, '', False) |
103 |
|
104 |
104 |
105 def expand(self, mobj, path, node): |
105 def expand(self, mobj, path, node): |
106 '''Expands keyword with corresponding template.''' |
106 '''Expands keyword with corresponding template.''' |
107 kw = mobj.group(1) |
107 kw = mobj.group(1) |
108 template = templater.parsestring(self.templates[kw], quoted=False) |
108 template = templater.parsestring(self.templates[kw], quoted=False) |
109 self.t.use_template(template) |
109 self.t.use_template(template) |
110 self.ui.pushbuffer() |
110 self.ui.pushbuffer() |
111 self.t.show(changenode=node, root=self.repo.root, file=path) |
111 self.t.show(changenode=node, root=self.repo.root, file=path) |
112 expansion = templater.firstline(self.ui.popbuffer()) |
112 kwsub = templater.firstline(self.ui.popbuffer()) |
113 return '$%s: %s $' % (kw, expansion) |
113 return '$%s: %s $' % (kw, kwsub) |
114 |
114 |
115 |
115 |
116 def reposetup(ui, repo): |
116 def reposetup(ui, repo): |
117 '''Sets up repo, and filelog especially, as kwrepo and kwfilelog |
117 '''Sets up repo, and filelog especially, as kwrepo and kwfilelog |
118 for keyword substitution. This is done for local repos only.''' |
118 for keyword substitution. This is done for local repos only.''' |
119 |
|
120 from mercurial import context, filelog, revlog |
|
121 if not repo.local(): |
119 if not repo.local(): |
|
120 return |
|
121 |
|
122 inc, exc = getkwconfig(ui, repo) |
|
123 if not inc: |
|
124 # no files configured for keyword substitution: |
|
125 # no need to burden repo with extra ballast |
122 return |
126 return |
123 |
127 |
124 class kwrepo(repo.__class__): |
128 class kwrepo(repo.__class__): |
125 def file(self, f): |
129 def file(self, f): |
126 if f[0] == '/': |
130 if f[0] == '/': |
135 def __init__(self, opener, path, repo, |
139 def __init__(self, opener, path, repo, |
136 defversion=revlog.REVLOG_DEFAULT_VERSION): |
140 defversion=revlog.REVLOG_DEFAULT_VERSION): |
137 super(kwfilelog, self).__init__(opener, path, defversion) |
141 super(kwfilelog, self).__init__(opener, path, defversion) |
138 self._repo = repo |
142 self._repo = repo |
139 self._path = path |
143 self._path = path |
140 # only init kwtemplater if needed |
144 # check at init if file configured for keyword substition |
141 if not isinstance(repo, int) and kwfmatches(ui, repo, [path]): |
145 if not isinstance(repo, int) and repo.kwfmatcher(path): |
142 self.kwt = kwtemplater(ui, repo) |
146 self.kwsub = True |
143 else: |
147 else: |
144 self.kwt = None |
148 self.kwsub = False |
145 |
149 |
146 def iskwcandidate(self, data): |
150 def iskwcandidate(self, data): |
147 '''Decides whether to act on keywords.''' |
151 '''Decides whether to act on keywords.''' |
148 return self.kwt is not None and not util.binary(data) |
152 return self.kwsub and not util.binary(data) |
149 |
153 |
150 def read(self, node): |
154 def read(self, node): |
151 '''Substitutes keywords when reading filelog.''' |
155 '''Substitutes keywords when reading filelog.''' |
152 data = super(kwfilelog, self).read(node) |
156 data = super(kwfilelog, self).read(node) |
153 if self.iskwcandidate(data): |
157 if self.iskwcandidate(data): |
154 c = context.filectx(self._repo, self._path, |
158 c = context.filectx(self._repo, self._path, |
155 fileid=node, filelog=self) |
159 fileid=node, filelog=self) |
156 return self.kwt.re_kw.sub(lambda m: |
160 return self._repo.kwt.re_kw.sub(lambda m: |
157 self.kwt.expand(m, self._path, c.node()), data) |
161 self._repo.kwt.expand(m, self._path, c.node()), data) |
158 return data |
162 return data |
159 |
163 |
160 def add(self, text, meta, tr, link, p1=None, p2=None): |
164 def add(self, text, meta, tr, link, p1=None, p2=None): |
161 '''Removes keyword substitutions when adding to filelog.''' |
165 '''Removes keyword substitutions when adding to filelog.''' |
162 if self.iskwcandidate(text): |
166 if self.iskwcandidate(text): |
163 text = self.kwt.re_kw.sub(r'$\1$', text) |
167 text = self._repo.kwt.re_kw.sub(r'$\1$', text) |
164 return super(kwfilelog, self).add(text, |
168 return super(kwfilelog, self).add(text, |
165 meta, tr, link, p1=p1, p2=p2) |
169 meta, tr, link, p1=p1, p2=p2) |
166 |
170 |
167 def cmp(self, node, text): |
171 def cmp(self, node, text): |
168 '''Removes keyword substitutions for comparison.''' |
172 '''Removes keyword substitutions for comparison.''' |
169 if self.iskwcandidate(text): |
173 if self.iskwcandidate(text): |
170 text = self.kwt.re_kw.sub(r'$\1$', text) |
174 text = self._repo.kwt.re_kw.sub(r'$\1$', text) |
171 return super(kwfilelog, self).cmp(node, text) |
175 return super(kwfilelog, self).cmp(node, text) |
172 |
176 |
173 filelog.filelog = kwfilelog |
177 filelog.filelog = kwfilelog |
174 repo.__class__ = kwrepo |
178 repo.__class__ = kwrepo |
|
179 |
|
180 # create filematching function once for repo |
|
181 setattr(repo, 'kwfmatcher', |
|
182 util.matcher(repo.root, inc=inc, exc=['.hg*']+exc)[1]) |
|
183 # initialize kwtemplater once for repo |
|
184 setattr(repo, 'kwt', kwtemplater(ui, repo)) |
|
185 |
175 # make pretxncommit hook import kwmodule regardless of where it's located |
186 # make pretxncommit hook import kwmodule regardless of where it's located |
176 for k, v in sys.modules.iteritems(): |
187 for k, v in sys.modules.iteritems(): |
177 if v is None: |
188 if v is None: |
178 continue |
189 continue |
179 if not hasattr(v, '__file__'): |
190 if not hasattr(v, '__file__'): |
183 break |
194 break |
184 else: |
195 else: |
185 sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) |
196 sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) |
186 mod = os.path.splitext(os.path.basename(__file__))[0] |
197 mod = os.path.splitext(os.path.basename(__file__))[0] |
187 ui.setconfig('hooks', 'pretxncommit.keyword', 'python:%s.pretxnkw' % mod) |
198 ui.setconfig('hooks', 'pretxncommit.keyword', 'python:%s.pretxnkw' % mod) |
188 del mod |
199 |
|
200 del inc, exc, mod |
189 |
201 |
190 |
202 |
191 def pretxnkw(ui, repo, hooktype, **args): |
203 def pretxnkw(ui, repo, hooktype, **args): |
192 '''pretxncommit hook that collects candidates for keyword expansion |
204 '''pretxncommit hook that collects candidates for keyword expansion |
193 on commit and expands keywords in working dir.''' |
205 on commit and expands keywords in working dir.''' |
194 from mercurial import commands |
|
195 |
206 |
196 cmd, sysargs, globalopts, cmdopts = commands.parse(ui, sys.argv[1:])[1:] |
207 cmd, sysargs, globalopts, cmdopts = commands.parse(ui, sys.argv[1:])[1:] |
197 if repr(cmd).split()[1] in ('tag', 'import_'): |
208 if repr(cmd).split()[1] in ('tag', 'import_'): |
198 return |
209 return |
199 |
210 |
200 files, match, anypats = cmdutil.matchpats(repo, sysargs, cmdopts) |
211 files, match, anypats = cmdutil.matchpats(repo, sysargs, cmdopts) |
201 modified, added = repo.status(files=files, match=match)[:2] |
212 modified, added = repo.status(files=files, match=match)[:2] |
202 candidates = kwfmatches(ui, repo, modified+added) |
213 candidates = [f for f in modified+added if repo.kwfmatcher(f)] |
203 if not candidates: |
214 if not candidates: |
204 return |
215 return |
205 |
216 |
206 kwt = kwtemplater(ui, repo) |
|
207 node = bin(args['node']) |
217 node = bin(args['node']) |
208 for f in candidates: |
218 for f in candidates: |
209 data = repo.wfile(f).read() |
219 data = repo.wfile(f).read() |
210 if not util.binary(data): |
220 if not util.binary(data): |
211 data, kwct = kwt.re_kw.subn(lambda m: kwt.expand(m, f, node), data) |
221 data, kwct = repo.kwt.re_kw.subn(lambda m: |
|
222 repo.kwt.expand(m, f, node), data) |
212 if kwct: |
223 if kwct: |
213 ui.debug(_('overwriting %s expanding keywords\n' % f)) |
224 ui.debug(_('overwriting %s expanding keywords\n' % f)) |
214 repo.wfile(f, 'w').write(data) |
225 repo.wfile(f, 'w').write(data) |