100 |
100 |
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 |
|
106 # make keyword tools accessible |
105 # make keyword tools accessible |
107 kwtools = {'templater': None, 'hgcmd': None} |
106 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']} |
108 |
|
109 # store originals of monkeypatches |
|
110 _patchfile_init = patch.patchfile.__init__ |
|
111 _patch_diff = patch.diff |
|
112 _dispatch_parse = dispatch._parse |
|
113 |
|
114 def _kwpatchfile_init(self, ui, fname, missing=False): |
|
115 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid |
|
116 rejects or conflicts due to expanded keywords in working dir.''' |
|
117 _patchfile_init(self, ui, fname, missing=missing) |
|
118 # shrink keywords read from working dir |
|
119 kwt = kwtools['templater'] |
|
120 self.lines = kwt.shrinklines(self.fname, self.lines) |
|
121 |
|
122 def _kw_diff(repo, node1=None, node2=None, files=None, match=util.always, |
|
123 fp=None, changes=None, opts=None): |
|
124 '''Monkeypatch patch.diff to avoid expansion except when |
|
125 comparing against working dir.''' |
|
126 if node2 is not None: |
|
127 kwtools['templater'].matcher = util.never |
|
128 elif node1 is not None and node1 != repo.changectx().node(): |
|
129 kwtools['templater'].restrict = True |
|
130 _patch_diff(repo, node1=node1, node2=node2, files=files, match=match, |
|
131 fp=fp, changes=changes, opts=opts) |
|
132 |
|
133 # monkeypatching hgweb functions changeset and filediff |
|
134 # actual monkeypatching is done at the bottom of reposetup() |
|
135 |
|
136 web_changeset = webcommands.changeset |
|
137 web_filediff = webcommands.filediff |
|
138 |
|
139 def _kwweb_changeset(web, req, tmpl): |
|
140 '''Wraps webcommands.changeset turning off keyword expansion.''' |
|
141 kwtools['templater'].matcher = util.never |
|
142 return web_changeset(web, req, tmpl) |
|
143 |
|
144 def _kwweb_filediff(web, req, tmpl): |
|
145 '''Wraps webcommands.filediff turning off keyword expansion.''' |
|
146 kwtools['templater'].matcher = util.never |
|
147 return web_filediff(web, req, tmpl) |
|
148 |
|
149 def _kwdispatch_parse(ui, args): |
|
150 '''Monkeypatch dispatch._parse to obtain running hg command.''' |
|
151 cmd, func, args, options, cmdoptions = _dispatch_parse(ui, args) |
|
152 kwtools['hgcmd'] = cmd |
|
153 return cmd, func, args, options, cmdoptions |
|
154 |
|
155 # dispatch._parse is run before reposetup, so wrap it here |
|
156 dispatch._parse = _kwdispatch_parse |
|
157 |
107 |
158 |
108 |
159 class kwtemplater(object): |
109 class kwtemplater(object): |
160 ''' |
110 ''' |
161 Sets up keyword templates, corresponding keyword regex, and |
111 Sets up keyword templates, corresponding keyword regex, and |
169 'Source': '{root}/{file},v', |
119 'Source': '{root}/{file},v', |
170 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}', |
120 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}', |
171 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}', |
121 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}', |
172 } |
122 } |
173 |
123 |
174 def __init__(self, ui, repo, inc, exc): |
124 def __init__(self, ui, repo): |
175 self.ui = ui |
125 self.ui = ui |
176 self.repo = repo |
126 self.repo = repo |
177 self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1] |
127 self.matcher = util.matcher(repo.root, |
|
128 inc=kwtools['inc'], exc=kwtools['exc'])[1] |
178 self.restrict = kwtools['hgcmd'] in restricted.split() |
129 self.restrict = kwtools['hgcmd'] in restricted.split() |
179 |
130 |
180 kwmaps = self.ui.configitems('keywordmaps') |
131 kwmaps = self.ui.configitems('keywordmaps') |
181 if kwmaps: # override default templates |
132 if kwmaps: # override default templates |
182 kwmaps = [(k, templater.parsestring(v, quoted=False)) |
133 kwmaps = [(k, templater.parsestring(v, False)) |
183 for (k, v) in kwmaps] |
134 for (k, v) in kwmaps] |
184 self.templates = dict(kwmaps) |
135 self.templates = dict(kwmaps) |
185 escaped = map(re.escape, self.templates.keys()) |
136 escaped = map(re.escape, self.templates.keys()) |
186 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped) |
137 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped) |
187 self.re_kw = re.compile(kwpat) |
138 self.re_kw = re.compile(kwpat) |
218 '''Returns true if path matches [keyword] pattern |
169 '''Returns true if path matches [keyword] pattern |
219 and is not a symbolic link. |
170 and is not a symbolic link. |
220 Caveat: localrepository._link fails on Windows.''' |
171 Caveat: localrepository._link fails on Windows.''' |
221 return self.matcher(path) and not islink(path) |
172 return self.matcher(path) and not islink(path) |
222 |
173 |
223 def overwrite(self, node=None, expand=True, files=None): |
174 def overwrite(self, node, expand, files): |
224 '''Overwrites selected files expanding/shrinking keywords.''' |
175 '''Overwrites selected files expanding/shrinking keywords.''' |
225 ctx = self.repo.changectx(node) |
176 ctx = self.repo.changectx(node) |
226 mf = ctx.manifest() |
177 mf = ctx.manifest() |
227 if node is not None: # commit |
178 if node is not None: # commit |
228 files = [f for f in ctx.files() if f in mf] |
179 files = [f for f in ctx.files() if f in mf] |
277 class kwfilelog(filelog.filelog): |
228 class kwfilelog(filelog.filelog): |
278 ''' |
229 ''' |
279 Subclass of filelog to hook into its read, add, cmp methods. |
230 Subclass of filelog to hook into its read, add, cmp methods. |
280 Keywords are "stored" unexpanded, and processed on reading. |
231 Keywords are "stored" unexpanded, and processed on reading. |
281 ''' |
232 ''' |
282 def __init__(self, opener, path): |
233 def __init__(self, opener, kwt, path): |
283 super(kwfilelog, self).__init__(opener, path) |
234 super(kwfilelog, self).__init__(opener, path) |
284 self.kwt = kwtools['templater'] |
235 self.kwt = kwt |
285 self.path = path |
236 self.path = path |
286 |
237 |
287 def read(self, node): |
238 def read(self, node): |
288 '''Expands keywords when reading filelog.''' |
239 '''Expands keywords when reading filelog.''' |
289 data = super(kwfilelog, self).read(node) |
240 data = super(kwfilelog, self).read(node) |
290 return self.kwt.expand(self.path, node, data) |
241 return self.kwt.expand(self.path, node, data) |
291 |
242 |
292 def add(self, text, meta, tr, link, p1=None, p2=None): |
243 def add(self, text, meta, tr, link, p1=None, p2=None): |
293 '''Removes keyword substitutions when adding to filelog.''' |
244 '''Removes keyword substitutions when adding to filelog.''' |
294 text = self.kwt.shrink(self.path, text) |
245 text = self.kwt.shrink(self.path, text) |
295 return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2) |
246 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2) |
296 |
247 |
297 def cmp(self, node, text): |
248 def cmp(self, node, text): |
298 '''Removes keyword substitutions for comparison.''' |
249 '''Removes keyword substitutions for comparison.''' |
299 text = self.kwt.shrink(self.path, text) |
250 text = self.kwt.shrink(self.path, text) |
300 if self.renamed(node): |
251 if self.renamed(node): |
351 kwstatus = 'current' |
302 kwstatus = 'current' |
352 fn = 'demo.txt' |
303 fn = 'demo.txt' |
353 branchname = 'demobranch' |
304 branchname = 'demobranch' |
354 tmpdir = tempfile.mkdtemp('', 'kwdemo.') |
305 tmpdir = tempfile.mkdtemp('', 'kwdemo.') |
355 ui.note(_('creating temporary repo at %s\n') % tmpdir) |
306 ui.note(_('creating temporary repo at %s\n') % tmpdir) |
356 repo = localrepo.localrepository(ui, path=tmpdir, create=True) |
307 repo = localrepo.localrepository(ui, tmpdir, True) |
357 ui.setconfig('keyword', fn, '') |
308 ui.setconfig('keyword', fn, '') |
358 if args or opts.get('rcfile'): |
309 if args or opts.get('rcfile'): |
359 kwstatus = 'custom' |
310 kwstatus = 'custom' |
360 if opts.get('rcfile'): |
311 if opts.get('rcfile'): |
361 ui.readconfig(opts.get('rcfile')) |
312 ui.readconfig(opts.get('rcfile')) |
373 fp.writelines(rcmaps) |
324 fp.writelines(rcmaps) |
374 fp.close() |
325 fp.close() |
375 ui.readconfig(repo.join('hgrc')) |
326 ui.readconfig(repo.join('hgrc')) |
376 if not opts.get('default'): |
327 if not opts.get('default'): |
377 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates |
328 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates |
|
329 uisetup(ui) |
378 reposetup(ui, repo) |
330 reposetup(ui, repo) |
379 for k, v in ui.configitems('extensions'): |
331 for k, v in ui.configitems('extensions'): |
380 if k.endswith('keyword'): |
332 if k.endswith('keyword'): |
381 extension = '%s = %s' % (k, v) |
333 extension = '%s = %s' % (k, v) |
382 break |
334 break |
454 ''' |
406 ''' |
455 # 3rd argument sets expansion to False |
407 # 3rd argument sets expansion to False |
456 _kwfwrite(ui, repo, False, *pats, **opts) |
408 _kwfwrite(ui, repo, False, *pats, **opts) |
457 |
409 |
458 |
410 |
|
411 def uisetup(ui): |
|
412 '''Collects [keyword] config in kwtools. |
|
413 Monkeypatches dispatch._parse if needed.''' |
|
414 |
|
415 for pat, opt in ui.configitems('keyword'): |
|
416 if opt != 'ignore': |
|
417 kwtools['inc'].append(pat) |
|
418 else: |
|
419 kwtools['exc'].append(pat) |
|
420 |
|
421 if kwtools['inc']: |
|
422 def kwdispatch_parse(ui, args): |
|
423 '''Monkeypatch dispatch._parse to obtain running hg command.''' |
|
424 cmd, func, args, options, cmdoptions = dispatch_parse(ui, args) |
|
425 kwtools['hgcmd'] = cmd |
|
426 return cmd, func, args, options, cmdoptions |
|
427 |
|
428 dispatch_parse = dispatch._parse |
|
429 dispatch._parse = kwdispatch_parse |
|
430 |
459 def reposetup(ui, repo): |
431 def reposetup(ui, repo): |
460 '''Sets up repo as kwrepo for keyword substitution. |
432 '''Sets up repo as kwrepo for keyword substitution. |
461 Overrides file method to return kwfilelog instead of filelog |
433 Overrides file method to return kwfilelog instead of filelog |
462 if file matches user configuration. |
434 if file matches user configuration. |
463 Wraps commit to overwrite configured files with updated |
435 Wraps commit to overwrite configured files with updated |
464 keyword substitutions. |
436 keyword substitutions. |
465 This is done for local repos only, and only if there are |
437 Monkeypatches patch and webcommands.''' |
466 files configured at all for keyword substitution.''' |
|
467 |
438 |
468 try: |
439 try: |
469 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split() |
440 if (not repo.local() or not kwtools['inc'] |
|
441 or kwtools['hgcmd'] in nokwcommands.split() |
470 or '.hg' in util.splitpath(repo.root) |
442 or '.hg' in util.splitpath(repo.root) |
471 or repo._url.startswith('bundle:')): |
443 or repo._url.startswith('bundle:')): |
472 return |
444 return |
473 except AttributeError: |
445 except AttributeError: |
474 pass |
446 pass |
475 |
447 |
476 inc, exc = [], ['.hg*'] |
448 kwtools['templater'] = kwt = kwtemplater(ui, repo) |
477 for pat, opt in ui.configitems('keyword'): |
|
478 if opt != 'ignore': |
|
479 inc.append(pat) |
|
480 else: |
|
481 exc.append(pat) |
|
482 if not inc: |
|
483 return |
|
484 |
|
485 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc) |
|
486 |
449 |
487 class kwrepo(repo.__class__): |
450 class kwrepo(repo.__class__): |
488 def file(self, f): |
451 def file(self, f): |
489 if f[0] == '/': |
452 if f[0] == '/': |
490 f = f[1:] |
453 f = f[1:] |
491 return kwfilelog(self.sopener, f) |
454 return kwfilelog(self.sopener, kwt, f) |
492 |
455 |
493 def wread(self, filename): |
456 def wread(self, filename): |
494 data = super(kwrepo, self).wread(filename) |
457 data = super(kwrepo, self).wread(filename) |
495 return kwt.wread(filename, data) |
458 return kwt.wread(filename, data) |
496 |
459 |
518 if _p2 == nullid: |
481 if _p2 == nullid: |
519 _p2 = '' |
482 _p2 = '' |
520 else: |
483 else: |
521 _p2 = hex(_p2) |
484 _p2 = hex(_p2) |
522 |
485 |
523 node = super(kwrepo, |
486 n = super(kwrepo, self).commit(files, text, user, date, match, |
524 self).commit(files=files, text=text, user=user, |
487 force, force_editor, p1, p2, |
525 date=date, match=match, force=force, |
488 extra, empty_ok) |
526 force_editor=force_editor, |
|
527 p1=p1, p2=p2, extra=extra, |
|
528 empty_ok=empty_ok) |
|
529 |
489 |
530 # restore commit hooks |
490 # restore commit hooks |
531 for name, cmd in commithooks.iteritems(): |
491 for name, cmd in commithooks.iteritems(): |
532 ui.setconfig('hooks', name, cmd) |
492 ui.setconfig('hooks', name, cmd) |
533 if node is not None: |
493 if n is not None: |
534 kwt.overwrite(node=node) |
494 kwt.overwrite(n, True, None) |
535 repo.hook('commit', node=node, parent1=_p1, parent2=_p2) |
495 repo.hook('commit', node=n, parent1=_p1, parent2=_p2) |
536 return node |
496 return n |
537 finally: |
497 finally: |
538 del wlock, lock |
498 del wlock, lock |
539 |
499 |
|
500 # monkeypatches |
|
501 def kwpatchfile_init(self, ui, fname, missing=False): |
|
502 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid |
|
503 rejects or conflicts due to expanded keywords in working dir.''' |
|
504 patchfile_init(self, ui, fname, missing) |
|
505 # shrink keywords read from working dir |
|
506 self.lines = kwt.shrinklines(self.fname, self.lines) |
|
507 |
|
508 def kw_diff(repo, node1=None, node2=None, files=None, match=util.always, |
|
509 fp=None, changes=None, opts=None): |
|
510 '''Monkeypatch patch.diff to avoid expansion except when |
|
511 comparing against working dir.''' |
|
512 if node2 is not None: |
|
513 kwt.matcher = util.never |
|
514 elif node1 is not None and node1 != repo.changectx().node(): |
|
515 kwt.restrict = True |
|
516 patch_diff(repo, node1, node2, files, match, fp, changes, opts) |
|
517 |
|
518 def kwweb_changeset(web, req, tmpl): |
|
519 '''Wraps webcommands.changeset turning off keyword expansion.''' |
|
520 kwt.matcher = util.never |
|
521 return webcommands_changeset(web, req, tmpl) |
|
522 |
|
523 def kwweb_filediff(web, req, tmpl): |
|
524 '''Wraps webcommands.filediff turning off keyword expansion.''' |
|
525 kwt.matcher = util.never |
|
526 return webcommands_filediff(web, req, tmpl) |
|
527 |
540 repo.__class__ = kwrepo |
528 repo.__class__ = kwrepo |
541 patch.patchfile.__init__ = _kwpatchfile_init |
529 |
542 patch.diff = _kw_diff |
530 patchfile_init = patch.patchfile.__init__ |
543 webcommands.changeset = webcommands.rev = _kwweb_changeset |
531 patch_diff = patch.diff |
544 webcommands.filediff = webcommands.diff = _kwweb_filediff |
532 webcommands_changeset = webcommands.changeset |
|
533 webcommands_filediff = webcommands.filediff |
|
534 |
|
535 patch.patchfile.__init__ = kwpatchfile_init |
|
536 patch.diff = kw_diff |
|
537 webcommands.changeset = webcommands.rev = kwweb_changeset |
|
538 webcommands.filediff = webcommands.diff = kwweb_filediff |
545 |
539 |
546 |
540 |
547 cmdtable = { |
541 cmdtable = { |
548 'kwdemo': |
542 'kwdemo': |
549 (demo, |
543 (demo, |