80 Or, better, use bundle/unbundle to share changes. |
80 Or, better, use bundle/unbundle to share changes. |
81 ''' |
81 ''' |
82 |
82 |
83 from mercurial import commands, cmdutil, context |
83 from mercurial import commands, cmdutil, context |
84 from mercurial import filelog, localrepo, revlog, templater, util |
84 from mercurial import filelog, localrepo, revlog, templater, util |
|
85 from mercurial.node import * |
85 from mercurial.i18n import gettext as _ |
86 from mercurial.i18n import gettext as _ |
86 import getopt, os.path, re, shutil, sys, tempfile, time |
87 import getopt, os.path, re, shutil, sys, tempfile, time |
87 |
88 |
88 # backwards compatibility hacks |
89 # backwards compatibility hacks |
|
90 |
|
91 def _wwrite(repo, f, data, mf): |
|
92 '''Makes repo.wwrite backwards compatible.''' |
|
93 # 656e06eebda7 removed file descriptor argument |
|
94 # 67982d3ee76c added flags argument |
|
95 try: |
|
96 repo.wwrite(f, data, mf.flags(f)) |
|
97 except (AttributeError, TypeError): |
|
98 repo.wwrite(f, data) |
|
99 |
|
100 def _normal(repo, files): |
|
101 '''Backwards compatible repo.dirstate.normal/update.''' |
|
102 # 6fd953d5faea introduced dirstate.normal() |
|
103 try: |
|
104 for f in files: |
|
105 repo.dirstate.normal(f) |
|
106 except AttributeError: |
|
107 repo.dirstate.update(files, 'n') |
89 |
108 |
90 def _pathto(repo, f, cwd=None): |
109 def _pathto(repo, f, cwd=None): |
91 '''kwfiles behaves similar to status, using pathto since 78b6add1f966.''' |
110 '''kwfiles behaves similar to status, using pathto since 78b6add1f966.''' |
92 try: |
111 try: |
93 return repo.pathto(f, cwd) |
112 return repo.pathto(f, cwd) |
160 'Source': '{root}/{file},v', |
181 'Source': '{root}/{file},v', |
161 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}', |
182 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}', |
162 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}', |
183 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}', |
163 } |
184 } |
164 |
185 |
165 def __init__(self, ui, repo, expand, path='', node=None): |
186 def __init__(self, ui, repo, inc, exc): |
166 self.ui = ui |
187 self.ui = ui |
167 self.repo = repo |
188 self.repo = repo |
168 self.t = expand or None |
189 self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1] |
169 self.path = path |
190 self.node = None |
170 self.node = node |
191 self.path = '' |
171 |
192 |
172 kwmaps = self.ui.configitems('keywordmaps') |
193 kwmaps = self.ui.configitems('keywordmaps') |
173 if kwmaps: # override default templates |
194 if kwmaps: # override default templates |
174 kwmaps = [(k, templater.parsestring(v, quoted=False)) |
195 kwmaps = [(k, templater.parsestring(v, quoted=False)) |
175 for (k, v) in kwmaps] |
196 for (k, v) in kwmaps] |
176 self.templates = dict(kwmaps) |
197 self.templates = dict(kwmaps) |
177 escaped = map(re.escape, self.templates.keys()) |
198 escaped = map(re.escape, self.templates.keys()) |
178 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped) |
199 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped) |
179 self.re_kw = re.compile(kwpat) |
200 self.re_kw = re.compile(kwpat) |
180 if self.t: |
201 |
181 templater.common_filters['utcdate'] = utcdate |
202 templater.common_filters['utcdate'] = utcdate |
182 self.t = self._changeset_templater() |
203 self.ct = self._changeset_templater() |
183 |
204 |
184 def _changeset_templater(self): |
205 def _changeset_templater(self): |
185 '''Backwards compatible cmdutil.changeset_templater.''' |
206 '''Backwards compatible cmdutil.changeset_templater.''' |
186 # before 1e0b94cfba0e there was an extra "brinfo" argument |
207 # before 1e0b94cfba0e there was an extra "brinfo" argument |
187 try: |
208 try: |
189 False, '', False) |
210 False, '', False) |
190 except TypeError: |
211 except TypeError: |
191 return cmdutil.changeset_templater(self.ui, self.repo, |
212 return cmdutil.changeset_templater(self.ui, self.repo, |
192 False, None, '', False) |
213 False, None, '', False) |
193 |
214 |
194 def _wwrite(self, f, data, man): |
|
195 '''Makes repo.wwrite backwards compatible.''' |
|
196 # 656e06eebda7 removed file descriptor argument |
|
197 # 67982d3ee76c added flags argument |
|
198 try: |
|
199 self.repo.wwrite(f, data, man.flags(f)) |
|
200 except (AttributeError, TypeError): |
|
201 self.repo.wwrite(f, data) |
|
202 |
|
203 def _normal(self, files): |
|
204 '''Backwards compatible repo.dirstate.normal/update.''' |
|
205 # 6fd953d5faea introduced dirstate.normal() |
|
206 try: |
|
207 for f in files: |
|
208 self.repo.dirstate.normal(f) |
|
209 except AttributeError: |
|
210 self.repo.dirstate.update(files, 'n') |
|
211 |
|
212 def substitute(self, node, data, subfunc): |
215 def substitute(self, node, data, subfunc): |
213 '''Obtains node if missing, and calls given substitution function.''' |
216 '''Obtains node if missing, and calls given substitution function.''' |
214 if not self.node: |
217 if not self.node: |
215 c = context.filectx(self.repo, self.path, fileid=node) |
218 c = context.filectx(self.repo, self.path, fileid=node) |
216 self.node = c.node() |
219 self.node = c.node() |
217 |
220 |
218 def kwsub(mobj): |
221 def kwsub(mobj): |
219 '''Substitutes keyword using corresponding template.''' |
222 '''Substitutes keyword using corresponding template.''' |
220 kw = mobj.group(1) |
223 kw = mobj.group(1) |
221 self.t.use_template(self.templates[kw]) |
224 self.ct.use_template(self.templates[kw]) |
222 self.ui.pushbuffer() |
225 self.ui.pushbuffer() |
223 self.t.show(changenode=self.node, |
226 self.ct.show(changenode=self.node, |
224 root=self.repo.root, file=self.path) |
227 root=self.repo.root, file=self.path) |
225 return '$%s: %s $' % (kw, templater.firstline(self.ui.popbuffer())) |
228 return '$%s: %s $' % (kw, templater.firstline(self.ui.popbuffer())) |
226 |
229 |
227 return subfunc(kwsub, data) |
230 return subfunc(kwsub, data) |
228 |
231 |
229 def expand(self, node, data): |
232 def expand(self, node, data): |
230 '''Returns data with keywords expanded.''' |
233 '''Returns data with keywords expanded.''' |
231 if util.binary(data): |
234 if util.binary(data): |
232 return data |
235 return data |
233 return self.substitute(node, data, self.re_kw.sub) |
236 return self.substitute(node, data, self.re_kw.sub) |
234 |
237 |
235 def process(self, node, data): |
238 def process(self, node, data, expand): |
236 '''Returns a tuple: data, count. |
239 '''Returns a tuple: data, count. |
237 Count is number of keywords/keyword substitutions, indicates |
240 Count is number of keywords/keyword substitutions, indicates |
238 to caller whether to act on file containing data. |
241 to caller whether to act on file containing data. |
239 Keywords in data are expanded, if templater was initialized.''' |
242 Keywords in data are expanded, if templater was initialized.''' |
240 if util.binary(data): |
243 if util.binary(data): |
241 return data, None |
244 return data, None |
242 if self.t: |
245 if expand: |
243 return self.substitute(node, data, self.re_kw.subn) |
246 return self.substitute(node, data, self.re_kw.subn) |
244 return data, self.re_kw.search(data) |
247 return data, self.re_kw.search(data) |
245 |
248 |
246 def shrink(self, text): |
249 def shrink(self, text): |
247 '''Returns text with all keyword substitutions removed.''' |
250 '''Returns text with all keyword substitutions removed.''' |
248 if util.binary(text): |
251 if util.binary(text): |
249 return text |
252 return text |
250 return self.re_kw.sub(r'$\1$', text) |
253 return self.re_kw.sub(r'$\1$', text) |
251 |
254 |
252 def overwrite(self, candidates, man, commit): |
|
253 '''Overwrites files in working directory if keywords are detected. |
|
254 Keywords are expanded if keyword templater is initialized, |
|
255 otherwise their substitution is removed.''' |
|
256 expand = self.t is not None |
|
257 action = ('shrinking', 'expanding')[expand] |
|
258 notify = (self.ui.note, self.ui.debug)[commit] |
|
259 overwritten = [] |
|
260 for f in candidates: |
|
261 fp = self.repo.file(f, kwexp=expand, kwmatch=True) |
|
262 data, kwfound = fp.kwctread(man[f]) |
|
263 if kwfound: |
|
264 notify(_('overwriting %s %s keywords\n') % (f, action)) |
|
265 self._wwrite(f, data, man) |
|
266 overwritten.append(f) |
|
267 self._normal(overwritten) |
|
268 |
|
269 class kwfilelog(filelog.filelog): |
255 class kwfilelog(filelog.filelog): |
270 ''' |
256 ''' |
271 Subclass of filelog to hook into its read, add, cmp methods. |
257 Subclass of filelog to hook into its read, add, cmp methods. |
272 Keywords are "stored" unexpanded, and processed on reading. |
258 Keywords are "stored" unexpanded, and processed on reading. |
273 ''' |
259 ''' |
274 def __init__(self, opener, path, kwtemplater): |
260 def __init__(self, opener, path, kwtemplater): |
275 super(kwfilelog, self).__init__(opener, path) |
261 super(kwfilelog, self).__init__(opener, path) |
276 self.kwtemplater = kwtemplater |
262 self.kwtemplater = kwtemplater |
277 |
263 self.kwtemplater.path = path |
278 def kwctread(self, node): |
264 |
|
265 def kwctread(self, node, expand): |
279 '''Reads expanding and counting keywords |
266 '''Reads expanding and counting keywords |
280 (only called from kwtemplater.overwrite).''' |
267 (only called from kwtemplater.overwrite).''' |
281 data = super(kwfilelog, self).read(node) |
268 data = super(kwfilelog, self).read(node) |
282 return self.kwtemplater.process(node, data) |
269 return self.kwtemplater.process(node, data, expand) |
283 |
270 |
284 def read(self, node): |
271 def read(self, node): |
285 '''Expands keywords when reading filelog.''' |
272 '''Expands keywords when reading filelog.''' |
286 data = super(kwfilelog, self).read(node) |
273 data = super(kwfilelog, self).read(node) |
287 return self.kwtemplater.expand(node, data) |
274 return self.kwtemplater.expand(node, data) |
297 if self.renamed(node): |
284 if self.renamed(node): |
298 t2 = super(kwfilelog, self).read(node) |
285 t2 = super(kwfilelog, self).read(node) |
299 return t2 != text |
286 return t2 != text |
300 return revlog.revlog.cmp(self, node, text) |
287 return revlog.revlog.cmp(self, node, text) |
301 |
288 |
302 def _status(ui, repo, *pats, **opts): |
289 def _iskwfile(f, kwtemplater, link): |
|
290 return not link(f) and kwtemplater.matcher(f) |
|
291 |
|
292 def _status(ui, repo, kwtemplater, *pats, **opts): |
303 '''Bails out if [keyword] configuration is not active. |
293 '''Bails out if [keyword] configuration is not active. |
304 Returns status of working directory.''' |
294 Returns status of working directory.''' |
305 if hasattr(ui, 'kwfmatcher'): |
295 if kwtemplater: |
306 files, match, anypats = cmdutil.matchpats(repo, pats, opts) |
296 files, match, anypats = cmdutil.matchpats(repo, pats, opts) |
307 return repo.status(files=files, match=match, list_clean=True) |
297 return repo.status(files=files, match=match, list_clean=True) |
308 if ui.configitems('keyword'): |
298 if ui.configitems('keyword'): |
309 raise util.Abort(_('[keyword] patterns cannot match')) |
299 raise util.Abort(_('[keyword] patterns cannot match')) |
310 raise util.Abort(_('no [keyword] patterns configured')) |
300 raise util.Abort(_('no [keyword] patterns configured')) |
311 |
301 |
312 def _iskwfile(ui, man, f): |
302 def _overwrite(ui, repo, kwtemplater, node=None, expand=True, files=None): |
313 return not man.linkf(f) and ui.kwfmatcher(f) |
303 '''Overwrites selected files expanding/shrinking keywords.''' |
314 |
304 ctx = repo.changectx(node) |
315 def _overwrite(ui, repo, files, node, man, expand, commit): |
305 mf = ctx.manifest() |
316 '''Passes given files to kwtemplater for overwriting.''' |
306 if files is None: |
317 files.sort() |
307 notify = ui.debug # commit |
318 kwt = kwtemplater(ui, repo, expand, node=node) |
308 files = [f for f in ctx.files() if mf.has_key(f)] |
319 kwt.overwrite(files, man, commit) |
309 else: |
|
310 notify = ui.note # kwexpand/kwshrink |
|
311 candidates = [f for f in files if _iskwfile(f, kwtemplater, mf.linkf)] |
|
312 if candidates: |
|
313 overwritten = [] |
|
314 candidates.sort() |
|
315 action = expand and 'expanding' or 'shrinking' |
|
316 kwtemplater.node = node or ctx.node() |
|
317 for f in candidates: |
|
318 fp = repo.file(f, kwmatch=True) |
|
319 data, kwfound = fp.kwctread(mf[f], expand) |
|
320 if kwfound: |
|
321 notify(_('overwriting %s %s keywords\n') % (f, action)) |
|
322 _wwrite(repo, f, data, mf) |
|
323 overwritten.append(f) |
|
324 _normal(repo, overwritten) |
320 |
325 |
321 def _kwfwrite(ui, repo, expand, *pats, **opts): |
326 def _kwfwrite(ui, repo, expand, *pats, **opts): |
322 '''Selects files and passes them to _overwrite.''' |
327 '''Selects files and passes them to _overwrite.''' |
323 status = _status(ui, repo, *pats, **opts) |
328 global _kwtemplater |
|
329 status = _status(ui, repo, _kwtemplater, *pats, **opts) |
324 modified, added, removed, deleted, unknown, ignored, clean = status |
330 modified, added, removed, deleted, unknown, ignored, clean = status |
325 if modified or added or removed or deleted: |
331 if modified or added or removed or deleted: |
326 raise util.Abort(_('outstanding uncommitted changes in given files')) |
332 raise util.Abort(_('outstanding uncommitted changes in given files')) |
327 wlock = lock = None |
333 wlock = lock = None |
328 try: |
334 try: |
329 wlock = repo.wlock() |
335 wlock = repo.wlock() |
330 lock = repo.lock() |
336 lock = repo.lock() |
331 ctx = repo.changectx() |
337 _overwrite(ui, repo, _kwtemplater, expand=expand, files=clean) |
332 man = ctx.manifest() |
|
333 candidates = [f for f in clean if _iskwfile(ui, man, f)] |
|
334 if candidates: |
|
335 # 7th argument sets commit to False |
|
336 _overwrite(ui, repo, candidates, ctx.node(), man, expand, False) |
|
337 finally: |
338 finally: |
338 del wlock, lock |
339 del wlock, lock |
339 |
340 |
340 |
341 |
341 def demo(ui, repo, *args, **opts): |
342 def demo(ui, repo, *args, **opts): |
405 quiet = ui.quiet |
406 quiet = ui.quiet |
406 verbose = ui.verbose |
407 verbose = ui.verbose |
407 ui.quiet = not verbose |
408 ui.quiet = not verbose |
408 commands.branch(ui, repo, branchname) |
409 commands.branch(ui, repo, branchname) |
409 ui.quiet = quiet |
410 ui.quiet = quiet |
|
411 for name, cmd in ui.configitems('hooks'): |
|
412 if name.split('.', 1)[0].find('commit') > -1: |
|
413 repo.ui.setconfig('hooks', name, '') |
|
414 ui.note(_('unhooked all commit hooks\n')) |
410 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg)) |
415 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg)) |
411 repo.commit(text=msg) |
416 repo.commit(text=msg) |
412 pathinfo = ('', ' in %s' % path)[ui.verbose] |
417 format = ui.verbose and ' in %s' % path or '' |
413 demostatus('%s keywords expanded%s' % (kwstatus, pathinfo)) |
418 demostatus('%s keywords expanded%s' % (kwstatus, format)) |
414 ui.write(repo.wread(fn)) |
419 ui.write(repo.wread(fn)) |
415 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir) |
420 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir) |
416 shutil.rmtree(tmpdir, ignore_errors=True) |
421 shutil.rmtree(tmpdir, ignore_errors=True) |
417 |
422 |
418 def expand(ui, repo, *pats, **opts): |
423 def expand(ui, repo, *pats, **opts): |
430 |
435 |
431 Crosscheck which files in working directory are potential targets for |
436 Crosscheck which files in working directory are potential targets for |
432 keyword expansion. |
437 keyword expansion. |
433 That is, files matched by [keyword] config patterns but not symlinks. |
438 That is, files matched by [keyword] config patterns but not symlinks. |
434 ''' |
439 ''' |
435 status = _status(ui, repo, *pats, **opts) |
440 global _kwtemplater |
|
441 status = _status(ui, repo, _kwtemplater, *pats, **opts) |
436 modified, added, removed, deleted, unknown, ignored, clean = status |
442 modified, added, removed, deleted, unknown, ignored, clean = status |
437 if opts['untracked']: |
443 if opts['untracked']: |
438 files = modified + added + unknown + clean |
444 files = modified + added + unknown + clean |
439 else: |
445 else: |
440 files = modified + added + clean |
446 files = modified + added + clean |
441 files.sort() |
447 files.sort() |
442 # use the full definition of repo._link for backwards compatibility |
448 # use the full definition of repo._link for backwards compatibility |
443 kwfiles = [f for f in files if ui.kwfmatcher(f) |
449 kwfiles = [f for f in files if _kwtemplater.matcher(f) |
444 and not os.path.islink(repo.wjoin(f))] |
450 and not os.path.islink(repo.wjoin(f))] |
445 cwd = pats and repo.getcwd() or '' |
451 cwd = pats and repo.getcwd() or '' |
446 allf = opts['all'] |
452 allf = opts['all'] |
447 ignore = opts['ignore'] |
453 ignore = opts['ignore'] |
448 if ignore: |
454 if ignore: |
501 else: |
504 else: |
502 exc.append(pat) |
505 exc.append(pat) |
503 if not inc: |
506 if not inc: |
504 return |
507 return |
505 |
508 |
506 ui.kwfmatcher = util.matcher(repo.root, inc=inc, exc=exc)[1] |
509 global _kwtemplater |
|
510 _kwtemplater = kwtemplater(ui, repo, inc, exc) |
507 |
511 |
508 class kwrepo(repo.__class__): |
512 class kwrepo(repo.__class__): |
509 def file(self, f, kwexp=True, kwmatch=False): |
513 def file(self, f, kwmatch=False): |
510 if f[0] == '/': |
514 if f[0] == '/': |
511 f = f[1:] |
515 f = f[1:] |
512 if kwmatch or ui.kwfmatcher(f): |
516 if kwmatch or _kwtemplater.matcher(f): |
513 kwt = kwtemplater(ui, self, kwexp, path=f) |
517 return kwfilelog(self.sopener, f, _kwtemplater) |
514 return kwfilelog(self.sopener, f, kwt) |
|
515 return filelog.filelog(self.sopener, f) |
518 return filelog.filelog(self.sopener, f) |
516 |
519 |
517 def _commit(self, files, text, user, date, match, force, lock, wlock, |
520 def _commit(self, files, text, user, date, match, force, lock, wlock, |
518 force_editor, p1, p2, extra): |
521 force_editor, p1, p2, extra): |
519 '''Private commit wrapper for backwards compatibility.''' |
522 '''Private commit wrapper for backwards compatibility.''' |
538 # so they are None or what was passed to commit |
541 # so they are None or what was passed to commit |
539 # use private _(w)lock for deletion |
542 # use private _(w)lock for deletion |
540 _lock = lock |
543 _lock = lock |
541 _wlock = wlock |
544 _wlock = wlock |
542 del wlock, lock |
545 del wlock, lock |
|
546 _p1 = _p2 = None |
543 try: |
547 try: |
544 if not _wlock: |
548 if not _wlock: |
545 _wlock = self.wlock() |
549 _wlock = self.wlock() |
546 if not _lock: |
550 if not _lock: |
547 _lock = self.lock() |
551 _lock = self.lock() |
|
552 # store and postpone commit hooks |
|
553 commithooks = [] |
|
554 for name, cmd in ui.configitems('hooks'): |
|
555 if name.split('.', 1)[0] == 'commit': |
|
556 commithooks.append((name, cmd)) |
|
557 ui.setconfig('hooks', name, '') |
|
558 if commithooks: |
|
559 # store parents for commit hook environment |
|
560 if p1 is None: |
|
561 _p1, _p2 = repo.dirstate.parents() |
|
562 else: |
|
563 _p1, _p2 = p1, p2 or nullid |
|
564 _p1 = hex(_p1) |
|
565 if _p2 == nullid: |
|
566 _p2 = '' |
|
567 else: |
|
568 _p2 = hex(_p2) |
|
569 |
548 node = self._commit(files, text, user, date, match, force, |
570 node = self._commit(files, text, user, date, match, force, |
549 _lock, _wlock, force_editor, p1, p2, extra) |
571 _lock, _wlock, force_editor, p1, p2, extra) |
|
572 |
|
573 # restore commit hooks |
|
574 for name, cmd in commithooks: |
|
575 ui.setconfig('hooks', name, cmd) |
550 if node is not None: |
576 if node is not None: |
551 cl = self.changelog.read(node) |
577 _overwrite(ui, self, _kwtemplater, node=node) |
552 mn = self.manifest.read(cl[0]) |
578 repo.hook('commit', node=node, parent1=_p1, parent2=_p2) |
553 candidates = [f for f in cl[3] if mn.has_key(f) |
|
554 and _iskwfile(ui, mn, f)] |
|
555 if candidates: |
|
556 # 6th, 7th arguments set expansion, commit to True |
|
557 _overwrite(ui, self, candidates, node, mn, True, True) |
|
558 return node |
579 return node |
559 finally: |
580 finally: |
560 del _wlock, _lock |
581 del _wlock, _lock |
561 |
582 |
562 repo.__class__ = kwrepo |
583 repo.__class__ = kwrepo |