101 def utcdate(date): |
101 def utcdate(date): |
102 '''Returns hgdate in cvs-like UTC format.''' |
102 '''Returns hgdate in cvs-like UTC format.''' |
103 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0])) |
103 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0])) |
104 |
104 |
105 |
105 |
106 _kwtemplater = _cmd = _cmdoptions = None |
106 # make keyword tools accessible |
107 |
107 kwtools = {'templater': None, 'hgcmd': None} |
|
108 |
108 # store originals of monkeypatches |
109 # store originals of monkeypatches |
109 _patchfile_init = patch.patchfile.__init__ |
110 _patchfile_init = patch.patchfile.__init__ |
110 _patch_diff = patch.diff |
111 _patch_diff = patch.diff |
111 _dispatch_parse = dispatch._parse |
112 _dispatch_parse = dispatch._parse |
112 |
113 |
113 def _kwpatchfile_init(self, ui, fname, missing=False): |
114 def _kwpatchfile_init(self, ui, fname, missing=False): |
114 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid |
115 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid |
115 rejects or conflicts due to expanded keywords in working dir.''' |
116 rejects or conflicts due to expanded keywords in working dir.''' |
116 _patchfile_init(self, ui, fname, missing=missing) |
117 _patchfile_init(self, ui, fname, missing=missing) |
117 if _kwtemplater.matcher(self.fname): |
118 # shrink keywords read from working dir |
118 # shrink keywords read from working dir |
119 kwt = kwtools['templater'] |
119 kwshrunk = _kwtemplater.shrink(''.join(self.lines)) |
120 self.lines = kwt.shrinklines(self.fname, self.lines) |
120 self.lines = kwshrunk.splitlines(True) |
|
121 |
121 |
122 def _kw_diff(repo, node1=None, node2=None, files=None, match=util.always, |
122 def _kw_diff(repo, node1=None, node2=None, files=None, match=util.always, |
123 fp=None, changes=None, opts=None): |
123 fp=None, changes=None, opts=None): |
124 # only expand if comparing against working dir |
124 '''Monkeypatch patch.diff to avoid expansion except when |
|
125 comparing against working dir.''' |
125 if node2 is not None: |
126 if node2 is not None: |
126 _kwtemplater.matcher = util.never |
127 kwtools['templater'].matcher = util.never |
127 if node1 is not None and node1 != repo.changectx().node(): |
128 elif node1 is not None and node1 != repo.changectx().node(): |
128 _kwtemplater.restrict = True |
129 kwtools['templater'].restrict = True |
129 _patch_diff(repo, node1=node1, node2=node2, files=files, match=match, |
130 _patch_diff(repo, node1=node1, node2=node2, files=files, match=match, |
130 fp=fp, changes=changes, opts=opts) |
131 fp=fp, changes=changes, opts=opts) |
131 |
132 |
132 def _kwweb_changeset(web, req, tmpl): |
133 def _kwweb_changeset(web, req, tmpl): |
133 '''Wraps webcommands.changeset turning off keyword expansion.''' |
134 '''Wraps webcommands.changeset turning off keyword expansion.''' |
134 _kwtemplater.matcher = util.never |
135 kwtools['templater'].matcher = util.never |
135 return web.changeset(tmpl, web.changectx(req)) |
136 return web.changeset(tmpl, web.changectx(req)) |
136 |
137 |
137 def _kwweb_filediff(web, req, tmpl): |
138 def _kwweb_filediff(web, req, tmpl): |
138 '''Wraps webcommands.filediff turning off keyword expansion.''' |
139 '''Wraps webcommands.filediff turning off keyword expansion.''' |
139 _kwtemplater.matcher = util.never |
140 kwtools['templater'].matcher = util.never |
140 return web.filediff(tmpl, web.filectx(req)) |
141 return web.filediff(tmpl, web.filectx(req)) |
141 |
142 |
142 def _kwdispatch_parse(ui, args): |
143 def _kwdispatch_parse(ui, args): |
143 '''Monkeypatch dispatch._parse to obtain running hg command.''' |
144 '''Monkeypatch dispatch._parse to obtain running hg command.''' |
144 global _cmd |
145 cmd, func, args, options, cmdoptions = _dispatch_parse(ui, args) |
145 _cmd, func, args, options, cmdoptions = _dispatch_parse(ui, args) |
146 kwtools['hgcmd'] = cmd |
146 return _cmd, func, args, options, cmdoptions |
147 return cmd, func, args, options, cmdoptions |
147 |
148 |
148 # dispatch._parse is run before reposetup, so wrap it here |
149 # dispatch._parse is run before reposetup, so wrap it here |
149 dispatch._parse = _kwdispatch_parse |
150 dispatch._parse = _kwdispatch_parse |
150 |
151 |
151 |
152 |
183 |
182 |
184 templatefilters.filters['utcdate'] = utcdate |
183 templatefilters.filters['utcdate'] = utcdate |
185 self.ct = cmdutil.changeset_templater(self.ui, self.repo, |
184 self.ct = cmdutil.changeset_templater(self.ui, self.repo, |
186 False, '', False) |
185 False, '', False) |
187 |
186 |
188 def substitute(self, node, data, subfunc): |
187 def getnode(self, path, fnode): |
189 '''Obtains file's changenode if commit node not given, |
188 '''Derives changenode from file path and filenode.''' |
190 and calls given substitution function.''' |
189 # used by kwfilelog.read and kwexpand |
191 if self.commitnode: |
190 c = context.filectx(self.repo, path, fileid=fnode) |
192 fnode = self.commitnode |
191 return c.node() |
193 else: |
192 |
194 c = context.filectx(self.repo, self.path, fileid=node) |
193 def substitute(self, data, path, node, subfunc): |
195 fnode = c.node() |
194 '''Replaces keywords in data with expanded template.''' |
196 |
|
197 def kwsub(mobj): |
195 def kwsub(mobj): |
198 '''Substitutes keyword using corresponding template.''' |
|
199 kw = mobj.group(1) |
196 kw = mobj.group(1) |
200 self.ct.use_template(self.templates[kw]) |
197 self.ct.use_template(self.templates[kw]) |
201 self.ui.pushbuffer() |
198 self.ui.pushbuffer() |
202 self.ct.show(changenode=fnode, root=self.repo.root, file=self.path) |
199 self.ct.show(changenode=node, root=self.repo.root, file=path) |
203 ekw = templatefilters.firstline(self.ui.popbuffer()) |
200 ekw = templatefilters.firstline(self.ui.popbuffer()) |
204 return '$%s: %s $' % (kw, ekw) |
201 return '$%s: %s $' % (kw, ekw) |
205 |
|
206 return subfunc(kwsub, data) |
202 return subfunc(kwsub, data) |
207 |
203 |
208 def expand(self, node, data): |
204 def expand(self, path, node, data): |
209 '''Returns data with keywords expanded.''' |
205 '''Returns data with keywords expanded.''' |
210 if self.restrict or util.binary(data): |
206 if not self.restrict and self.matcher(path) and not util.binary(data): |
211 return data |
207 changenode = self.getnode(path, node) |
212 return self.substitute(node, data, self.re_kw.sub) |
208 return self.substitute(data, path, changenode, self.re_kw.sub) |
213 |
209 return data |
214 def process(self, node, data, expand): |
210 |
215 '''Returns a tuple: data, count. |
211 def iskwfile(self, path, islink): |
216 Count is number of keywords/keyword substitutions, |
212 '''Returns true if path matches [keyword] pattern |
217 telling caller whether to act on file containing data.''' |
213 and is not a symbolic link. |
218 if util.binary(data): |
214 Caveat: localrepository._link fails on Windows.''' |
219 return data, None |
215 return self.matcher(path) and not islink(path) |
220 if expand: |
216 |
221 return self.substitute(node, data, self.re_kw.subn) |
217 def overwrite(self, node=None, expand=True, files=None): |
222 return data, self.re_kw.search(data) |
218 '''Overwrites selected files expanding/shrinking keywords.''' |
223 |
219 ctx = self.repo.changectx(node) |
224 def shrink(self, text): |
220 mf = ctx.manifest() |
|
221 if node is not None: # commit |
|
222 files = [f for f in ctx.files() if f in mf] |
|
223 notify = self.ui.debug |
|
224 else: # kwexpand/kwshrink |
|
225 notify = self.ui.note |
|
226 candidates = [f for f in files if self.iskwfile(f, mf.linkf)] |
|
227 if candidates: |
|
228 self.restrict = True # do not expand when reading |
|
229 candidates.sort() |
|
230 action = expand and 'expanding' or 'shrinking' |
|
231 for f in candidates: |
|
232 fp = self.repo.file(f) |
|
233 data = fp.read(mf[f]) |
|
234 if util.binary(data): |
|
235 continue |
|
236 if expand: |
|
237 changenode = node or self.getnode(f, mf[f]) |
|
238 data, found = self.substitute(data, f, changenode, |
|
239 self.re_kw.subn) |
|
240 else: |
|
241 found = self.re_kw.search(data) |
|
242 if found: |
|
243 notify(_('overwriting %s %s keywords\n') % (f, action)) |
|
244 self.repo.wwrite(f, data, mf.flags(f)) |
|
245 self.repo.dirstate.normal(f) |
|
246 self.restrict = False |
|
247 |
|
248 def shrinktext(self, text): |
|
249 '''Unconditionally removes all keyword substitutions from text.''' |
|
250 return self.re_kw.sub(r'$\1$', text) |
|
251 |
|
252 def shrink(self, fname, text): |
225 '''Returns text with all keyword substitutions removed.''' |
253 '''Returns text with all keyword substitutions removed.''' |
226 if util.binary(text): |
254 if self.matcher(fname) and not util.binary(text): |
227 return text |
255 return self.shrinktext(text) |
228 return self.re_kw.sub(r'$\1$', text) |
256 return text |
|
257 |
|
258 def shrinklines(self, fname, lines): |
|
259 '''Returns lines with keyword substitutions removed.''' |
|
260 if self.matcher(fname): |
|
261 text = ''.join(lines) |
|
262 if not util.binary(text): |
|
263 return self.shrinktext(text).splitlines(True) |
|
264 return lines |
|
265 |
|
266 def wread(self, fname, data): |
|
267 '''If in restricted mode returns data read from wdir with |
|
268 keyword substitutions removed.''' |
|
269 return self.restrict and self.shrink(fname, data) or data |
229 |
270 |
230 class kwfilelog(filelog.filelog): |
271 class kwfilelog(filelog.filelog): |
231 ''' |
272 ''' |
232 Subclass of filelog to hook into its read, add, cmp methods. |
273 Subclass of filelog to hook into its read, add, cmp methods. |
233 Keywords are "stored" unexpanded, and processed on reading. |
274 Keywords are "stored" unexpanded, and processed on reading. |
234 ''' |
275 ''' |
235 def __init__(self, opener, path): |
276 def __init__(self, opener, path): |
236 super(kwfilelog, self).__init__(opener, path) |
277 super(kwfilelog, self).__init__(opener, path) |
237 _kwtemplater.path = path |
278 self.kwt = kwtools['templater'] |
238 |
279 self.path = path |
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 |
280 |
244 def read(self, node): |
281 def read(self, node): |
245 '''Expands keywords when reading filelog.''' |
282 '''Expands keywords when reading filelog.''' |
246 data = super(kwfilelog, self).read(node) |
283 data = super(kwfilelog, self).read(node) |
247 return _kwtemplater.expand(node, data) |
284 return self.kwt.expand(self.path, node, data) |
248 |
285 |
249 def add(self, text, meta, tr, link, p1=None, p2=None): |
286 def add(self, text, meta, tr, link, p1=None, p2=None): |
250 '''Removes keyword substitutions when adding to filelog.''' |
287 '''Removes keyword substitutions when adding to filelog.''' |
251 text = _kwtemplater.shrink(text) |
288 text = self.kwt.shrink(self.path, text) |
252 return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2) |
289 return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2) |
253 |
290 |
254 def cmp(self, node, text): |
291 def cmp(self, node, text): |
255 '''Removes keyword substitutions for comparison.''' |
292 '''Removes keyword substitutions for comparison.''' |
256 text = _kwtemplater.shrink(text) |
293 text = self.kwt.shrink(self.path, text) |
257 if self.renamed(node): |
294 if self.renamed(node): |
258 t2 = super(kwfilelog, self).read(node) |
295 t2 = super(kwfilelog, self).read(node) |
259 return t2 != text |
296 return t2 != text |
260 return revlog.revlog.cmp(self, node, text) |
297 return revlog.revlog.cmp(self, node, text) |
261 |
298 |
262 def _iskwfile(f, link): |
299 def _status(ui, repo, kwt, *pats, **opts): |
263 return not link(f) and _kwtemplater.matcher(f) |
|
264 |
|
265 def _status(ui, repo, *pats, **opts): |
|
266 '''Bails out if [keyword] configuration is not active. |
300 '''Bails out if [keyword] configuration is not active. |
267 Returns status of working directory.''' |
301 Returns status of working directory.''' |
268 if _kwtemplater: |
302 if kwt: |
269 files, match, anypats = cmdutil.matchpats(repo, pats, opts) |
303 files, match, anypats = cmdutil.matchpats(repo, pats, opts) |
270 return repo.status(files=files, match=match, list_clean=True) |
304 return repo.status(files=files, match=match, list_clean=True) |
271 if ui.configitems('keyword'): |
305 if ui.configitems('keyword'): |
272 raise util.Abort(_('[keyword] patterns cannot match')) |
306 raise util.Abort(_('[keyword] patterns cannot match')) |
273 raise util.Abort(_('no [keyword] patterns configured')) |
307 raise util.Abort(_('no [keyword] patterns configured')) |
274 |
308 |
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): |
309 def _kwfwrite(ui, repo, expand, *pats, **opts): |
298 '''Selects files and passes them to _overwrite.''' |
310 '''Selects files and passes them to kwtemplater.overwrite.''' |
299 status = _status(ui, repo, *pats, **opts) |
311 kwt = kwtools['templater'] |
|
312 status = _status(ui, repo, kwt, *pats, **opts) |
300 modified, added, removed, deleted, unknown, ignored, clean = status |
313 modified, added, removed, deleted, unknown, ignored, clean = status |
301 if modified or added or removed or deleted: |
314 if modified or added or removed or deleted: |
302 raise util.Abort(_('outstanding uncommitted changes in given files')) |
315 raise util.Abort(_('outstanding uncommitted changes in given files')) |
303 wlock = lock = None |
316 wlock = lock = None |
304 try: |
317 try: |
305 wlock = repo.wlock() |
318 wlock = repo.wlock() |
306 lock = repo.lock() |
319 lock = repo.lock() |
307 _overwrite(ui, repo, expand=expand, files=clean) |
320 kwt.overwrite(expand=expand, files=clean) |
308 finally: |
321 finally: |
309 del wlock, lock |
322 del wlock, lock |
310 |
323 |
311 |
324 |
312 def demo(ui, repo, *args, **opts): |
325 def demo(ui, repo, *args, **opts): |