##// END OF EJS Templates
keyword: docstrings for additional date filters
Christian Ebert -
r13633:20d13244 default
parent child Browse files
Show More
@@ -1,685 +1,693 b''
1 1 # keyword.py - $Keyword$ expansion for Mercurial
2 2 #
3 3 # Copyright 2007-2010 Christian Ebert <blacktrash@gmx.net>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 #
8 8 # $Id$
9 9 #
10 10 # Keyword expansion hack against the grain of a DSCM
11 11 #
12 12 # There are many good reasons why this is not needed in a distributed
13 13 # SCM, still it may be useful in very small projects based on single
14 14 # files (like LaTeX packages), that are mostly addressed to an
15 15 # audience not running a version control system.
16 16 #
17 17 # For in-depth discussion refer to
18 18 # <http://mercurial.selenic.com/wiki/KeywordPlan>.
19 19 #
20 20 # Keyword expansion is based on Mercurial's changeset template mappings.
21 21 #
22 22 # Binary files are not touched.
23 23 #
24 24 # Files to act upon/ignore are specified in the [keyword] section.
25 25 # Customized keyword template mappings in the [keywordmaps] section.
26 26 #
27 27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
28 28
29 29 '''expand keywords in tracked files
30 30
31 31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
32 32 tracked text files selected by your configuration.
33 33
34 34 Keywords are only expanded in local repositories and not stored in the
35 35 change history. The mechanism can be regarded as a convenience for the
36 36 current user or for archive distribution.
37 37
38 38 Keywords expand to the changeset data pertaining to the latest change
39 39 relative to the working directory parent of each file.
40 40
41 41 Configuration is done in the [keyword], [keywordset] and [keywordmaps]
42 42 sections of hgrc files.
43 43
44 44 Example::
45 45
46 46 [keyword]
47 47 # expand keywords in every python file except those matching "x*"
48 48 **.py =
49 49 x* = ignore
50 50
51 51 [keywordset]
52 52 # prefer svn- over cvs-like default keywordmaps
53 53 svn = True
54 54
55 55 .. note::
56 56 The more specific you are in your filename patterns the less you
57 57 lose speed in huge repositories.
58 58
59 59 For [keywordmaps] template mapping and expansion demonstration and
60 60 control run :hg:`kwdemo`. See :hg:`help templates` for a list of
61 61 available templates and filters.
62 62
63 63 Three additional date template filters are provided::
64 64
65 65 utcdate "2006/09/18 15:13:13"
66 66 svnutcdate "2006-09-18 15:13:13Z"
67 67 svnisodate "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
68 68
69 69 The default template mappings (view with :hg:`kwdemo -d`) can be
70 70 replaced with customized keywords and templates. Again, run
71 71 :hg:`kwdemo` to control the results of your configuration changes.
72 72
73 73 Before changing/disabling active keywords, you must run :hg:`kwshrink`
74 74 to avoid storing expanded keywords in the change history.
75 75
76 76 To force expansion after enabling it, or a configuration change, run
77 77 :hg:`kwexpand`.
78 78
79 79 Expansions spanning more than one line and incremental expansions,
80 80 like CVS' $Log$, are not supported. A keyword template map "Log =
81 81 {desc}" expands to the first line of the changeset description.
82 82 '''
83 83
84 84 from mercurial import commands, context, cmdutil, dispatch, filelog, extensions
85 85 from mercurial import localrepo, match, patch, templatefilters, templater, util
86 86 from mercurial.hgweb import webcommands
87 87 from mercurial.i18n import _
88 88 import os, re, shutil, tempfile
89 89
90 90 commands.optionalrepo += ' kwdemo'
91 91
92 92 # hg commands that do not act on keywords
93 93 nokwcommands = ('add addremove annotate bundle export grep incoming init log'
94 94 ' outgoing push tip verify convert email glog')
95 95
96 96 # hg commands that trigger expansion only when writing to working dir,
97 97 # not when reading filelog, and unexpand when reading from working dir
98 98 restricted = 'merge kwexpand kwshrink record qrecord resolve transplant'
99 99
100 100 # names of extensions using dorecord
101 101 recordextensions = 'record'
102 102
103 103 colortable = {
104 104 'kwfiles.enabled': 'green bold',
105 105 'kwfiles.deleted': 'cyan bold underline',
106 106 'kwfiles.enabledunknown': 'green',
107 107 'kwfiles.ignored': 'bold',
108 108 'kwfiles.ignoredunknown': 'none'
109 109 }
110 110
111 111 # date like in cvs' $Date
112 112 def utcdate(text):
113 ''':utcdate: Date. Returns a UTC-date in this format: "2009/08/18 11:00:13".
114 '''
113 115 return util.datestr((text[0], 0), '%Y/%m/%d %H:%M:%S')
114 116 # date like in svn's $Date
115 117 def svnisodate(text):
118 ''':svnisodate: Date. Returns a date in this format: "2009-08-18 13:00:13
119 +0200 (Tue, 18 Aug 2009)".
120 '''
116 121 return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
117 122 # date like in svn's $Id
118 123 def svnutcdate(text):
124 ''':svnutcdate: Date. Returns a UTC-date in this format: "2009-08-18
125 11:00:13Z".
126 '''
119 127 return util.datestr((text[0], 0), '%Y-%m-%d %H:%M:%SZ')
120 128
121 129 # make keyword tools accessible
122 130 kwtools = {'templater': None, 'hgcmd': ''}
123 131
124 132 def _defaultkwmaps(ui):
125 133 '''Returns default keywordmaps according to keywordset configuration.'''
126 134 templates = {
127 135 'Revision': '{node|short}',
128 136 'Author': '{author|user}',
129 137 }
130 138 kwsets = ({
131 139 'Date': '{date|utcdate}',
132 140 'RCSfile': '{file|basename},v',
133 141 'RCSFile': '{file|basename},v', # kept for backwards compatibility
134 142 # with hg-keyword
135 143 'Source': '{root}/{file},v',
136 144 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
137 145 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
138 146 }, {
139 147 'Date': '{date|svnisodate}',
140 148 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
141 149 'LastChangedRevision': '{node|short}',
142 150 'LastChangedBy': '{author|user}',
143 151 'LastChangedDate': '{date|svnisodate}',
144 152 })
145 153 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
146 154 return templates
147 155
148 156 def _shrinktext(text, subfunc):
149 157 '''Helper for keyword expansion removal in text.
150 158 Depending on subfunc also returns number of substitutions.'''
151 159 return subfunc(r'$\1$', text)
152 160
153 161 def _preselect(wstatus, changed):
154 162 '''Retrieves modfied and added files from a working directory state
155 163 and returns the subset of each contained in given changed files
156 164 retrieved from a change context.'''
157 165 modified, added = wstatus[:2]
158 166 modified = [f for f in modified if f in changed]
159 167 added = [f for f in added if f in changed]
160 168 return modified, added
161 169
162 170
163 171 class kwtemplater(object):
164 172 '''
165 173 Sets up keyword templates, corresponding keyword regex, and
166 174 provides keyword substitution functions.
167 175 '''
168 176
169 177 def __init__(self, ui, repo, inc, exc):
170 178 self.ui = ui
171 179 self.repo = repo
172 180 self.match = match.match(repo.root, '', [], inc, exc)
173 181 self.restrict = kwtools['hgcmd'] in restricted.split()
174 182 self.record = False
175 183
176 184 kwmaps = self.ui.configitems('keywordmaps')
177 185 if kwmaps: # override default templates
178 186 self.templates = dict((k, templater.parsestring(v, False))
179 187 for k, v in kwmaps)
180 188 else:
181 189 self.templates = _defaultkwmaps(self.ui)
182 190 templatefilters.filters.update({'utcdate': utcdate,
183 191 'svnisodate': svnisodate,
184 192 'svnutcdate': svnutcdate})
185 193
186 194 @util.propertycache
187 195 def escape(self):
188 196 '''Returns bar-separated and escaped keywords.'''
189 197 return '|'.join(map(re.escape, self.templates.keys()))
190 198
191 199 @util.propertycache
192 200 def rekw(self):
193 201 '''Returns regex for unexpanded keywords.'''
194 202 return re.compile(r'\$(%s)\$' % self.escape)
195 203
196 204 @util.propertycache
197 205 def rekwexp(self):
198 206 '''Returns regex for expanded keywords.'''
199 207 return re.compile(r'\$(%s): [^$\n\r]*? \$' % self.escape)
200 208
201 209 def substitute(self, data, path, ctx, subfunc):
202 210 '''Replaces keywords in data with expanded template.'''
203 211 def kwsub(mobj):
204 212 kw = mobj.group(1)
205 213 ct = cmdutil.changeset_templater(self.ui, self.repo,
206 214 False, None, '', False)
207 215 ct.use_template(self.templates[kw])
208 216 self.ui.pushbuffer()
209 217 ct.show(ctx, root=self.repo.root, file=path)
210 218 ekw = templatefilters.firstline(self.ui.popbuffer())
211 219 return '$%s: %s $' % (kw, ekw)
212 220 return subfunc(kwsub, data)
213 221
214 222 def linkctx(self, path, fileid):
215 223 '''Similar to filelog.linkrev, but returns a changectx.'''
216 224 return self.repo.filectx(path, fileid=fileid).changectx()
217 225
218 226 def expand(self, path, node, data):
219 227 '''Returns data with keywords expanded.'''
220 228 if not self.restrict and self.match(path) and not util.binary(data):
221 229 ctx = self.linkctx(path, node)
222 230 return self.substitute(data, path, ctx, self.rekw.sub)
223 231 return data
224 232
225 233 def iskwfile(self, cand, ctx):
226 234 '''Returns subset of candidates which are configured for keyword
227 235 expansion are not symbolic links.'''
228 236 return [f for f in cand if self.match(f) and not 'l' in ctx.flags(f)]
229 237
230 238 def overwrite(self, ctx, candidates, lookup, expand, rekw=False):
231 239 '''Overwrites selected files expanding/shrinking keywords.'''
232 240 if self.restrict or lookup or self.record: # exclude kw_copy
233 241 candidates = self.iskwfile(candidates, ctx)
234 242 if not candidates:
235 243 return
236 244 kwcmd = self.restrict and lookup # kwexpand/kwshrink
237 245 if self.restrict or expand and lookup:
238 246 mf = ctx.manifest()
239 247 lctx = ctx
240 248 re_kw = (self.restrict or rekw) and self.rekw or self.rekwexp
241 249 msg = (expand and _('overwriting %s expanding keywords\n')
242 250 or _('overwriting %s shrinking keywords\n'))
243 251 for f in candidates:
244 252 if self.restrict:
245 253 data = self.repo.file(f).read(mf[f])
246 254 else:
247 255 data = self.repo.wread(f)
248 256 if util.binary(data):
249 257 continue
250 258 if expand:
251 259 if lookup:
252 260 lctx = self.linkctx(f, mf[f])
253 261 data, found = self.substitute(data, f, lctx, re_kw.subn)
254 262 elif self.restrict:
255 263 found = re_kw.search(data)
256 264 else:
257 265 data, found = _shrinktext(data, re_kw.subn)
258 266 if found:
259 267 self.ui.note(msg % f)
260 268 self.repo.wwrite(f, data, ctx.flags(f))
261 269 if kwcmd:
262 270 self.repo.dirstate.normal(f)
263 271 elif self.record:
264 272 self.repo.dirstate.normallookup(f)
265 273
266 274 def shrink(self, fname, text):
267 275 '''Returns text with all keyword substitutions removed.'''
268 276 if self.match(fname) and not util.binary(text):
269 277 return _shrinktext(text, self.rekwexp.sub)
270 278 return text
271 279
272 280 def shrinklines(self, fname, lines):
273 281 '''Returns lines with keyword substitutions removed.'''
274 282 if self.match(fname):
275 283 text = ''.join(lines)
276 284 if not util.binary(text):
277 285 return _shrinktext(text, self.rekwexp.sub).splitlines(True)
278 286 return lines
279 287
280 288 def wread(self, fname, data):
281 289 '''If in restricted mode returns data read from wdir with
282 290 keyword substitutions removed.'''
283 291 return self.restrict and self.shrink(fname, data) or data
284 292
285 293 class kwfilelog(filelog.filelog):
286 294 '''
287 295 Subclass of filelog to hook into its read, add, cmp methods.
288 296 Keywords are "stored" unexpanded, and processed on reading.
289 297 '''
290 298 def __init__(self, opener, kwt, path):
291 299 super(kwfilelog, self).__init__(opener, path)
292 300 self.kwt = kwt
293 301 self.path = path
294 302
295 303 def read(self, node):
296 304 '''Expands keywords when reading filelog.'''
297 305 data = super(kwfilelog, self).read(node)
298 306 if self.renamed(node):
299 307 return data
300 308 return self.kwt.expand(self.path, node, data)
301 309
302 310 def add(self, text, meta, tr, link, p1=None, p2=None):
303 311 '''Removes keyword substitutions when adding to filelog.'''
304 312 text = self.kwt.shrink(self.path, text)
305 313 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
306 314
307 315 def cmp(self, node, text):
308 316 '''Removes keyword substitutions for comparison.'''
309 317 text = self.kwt.shrink(self.path, text)
310 318 return super(kwfilelog, self).cmp(node, text)
311 319
312 320 def _status(ui, repo, kwt, *pats, **opts):
313 321 '''Bails out if [keyword] configuration is not active.
314 322 Returns status of working directory.'''
315 323 if kwt:
316 324 return repo.status(match=cmdutil.match(repo, pats, opts), clean=True,
317 325 unknown=opts.get('unknown') or opts.get('all'))
318 326 if ui.configitems('keyword'):
319 327 raise util.Abort(_('[keyword] patterns cannot match'))
320 328 raise util.Abort(_('no [keyword] patterns configured'))
321 329
322 330 def _kwfwrite(ui, repo, expand, *pats, **opts):
323 331 '''Selects files and passes them to kwtemplater.overwrite.'''
324 332 wctx = repo[None]
325 333 if len(wctx.parents()) > 1:
326 334 raise util.Abort(_('outstanding uncommitted merge'))
327 335 kwt = kwtools['templater']
328 336 wlock = repo.wlock()
329 337 try:
330 338 status = _status(ui, repo, kwt, *pats, **opts)
331 339 modified, added, removed, deleted, unknown, ignored, clean = status
332 340 if modified or added or removed or deleted:
333 341 raise util.Abort(_('outstanding uncommitted changes'))
334 342 kwt.overwrite(wctx, clean, True, expand)
335 343 finally:
336 344 wlock.release()
337 345
338 346 def demo(ui, repo, *args, **opts):
339 347 '''print [keywordmaps] configuration and an expansion example
340 348
341 349 Show current, custom, or default keyword template maps and their
342 350 expansions.
343 351
344 352 Extend the current configuration by specifying maps as arguments
345 353 and using -f/--rcfile to source an external hgrc file.
346 354
347 355 Use -d/--default to disable current configuration.
348 356
349 357 See :hg:`help templates` for information on templates and filters.
350 358 '''
351 359 def demoitems(section, items):
352 360 ui.write('[%s]\n' % section)
353 361 for k, v in sorted(items):
354 362 ui.write('%s = %s\n' % (k, v))
355 363
356 364 fn = 'demo.txt'
357 365 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
358 366 ui.note(_('creating temporary repository at %s\n') % tmpdir)
359 367 repo = localrepo.localrepository(ui, tmpdir, True)
360 368 ui.setconfig('keyword', fn, '')
361 369 svn = ui.configbool('keywordset', 'svn')
362 370 # explicitly set keywordset for demo output
363 371 ui.setconfig('keywordset', 'svn', svn)
364 372
365 373 uikwmaps = ui.configitems('keywordmaps')
366 374 if args or opts.get('rcfile'):
367 375 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
368 376 if uikwmaps:
369 377 ui.status(_('\textending current template maps\n'))
370 378 if opts.get('default') or not uikwmaps:
371 379 if svn:
372 380 ui.status(_('\toverriding default svn keywordset\n'))
373 381 else:
374 382 ui.status(_('\toverriding default cvs keywordset\n'))
375 383 if opts.get('rcfile'):
376 384 ui.readconfig(opts.get('rcfile'))
377 385 if args:
378 386 # simulate hgrc parsing
379 387 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
380 388 fp = repo.opener('hgrc', 'w')
381 389 fp.writelines(rcmaps)
382 390 fp.close()
383 391 ui.readconfig(repo.join('hgrc'))
384 392 kwmaps = dict(ui.configitems('keywordmaps'))
385 393 elif opts.get('default'):
386 394 if svn:
387 395 ui.status(_('\n\tconfiguration using default svn keywordset\n'))
388 396 else:
389 397 ui.status(_('\n\tconfiguration using default cvs keywordset\n'))
390 398 kwmaps = _defaultkwmaps(ui)
391 399 if uikwmaps:
392 400 ui.status(_('\tdisabling current template maps\n'))
393 401 for k, v in kwmaps.iteritems():
394 402 ui.setconfig('keywordmaps', k, v)
395 403 else:
396 404 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
397 405 kwmaps = dict(uikwmaps) or _defaultkwmaps(ui)
398 406
399 407 uisetup(ui)
400 408 reposetup(ui, repo)
401 409 ui.write('[extensions]\nkeyword =\n')
402 410 demoitems('keyword', ui.configitems('keyword'))
403 411 demoitems('keywordset', ui.configitems('keywordset'))
404 412 demoitems('keywordmaps', kwmaps.iteritems())
405 413 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
406 414 repo.wopener(fn, 'w').write(keywords)
407 415 repo[None].add([fn])
408 416 ui.note(_('\nkeywords written to %s:\n') % fn)
409 417 ui.note(keywords)
410 418 repo.dirstate.setbranch('demobranch')
411 419 for name, cmd in ui.configitems('hooks'):
412 420 if name.split('.', 1)[0].find('commit') > -1:
413 421 repo.ui.setconfig('hooks', name, '')
414 422 msg = _('hg keyword configuration and expansion example')
415 423 ui.note("hg ci -m '%s'\n" % msg)
416 424 repo.commit(text=msg)
417 425 ui.status(_('\n\tkeywords expanded\n'))
418 426 ui.write(repo.wread(fn))
419 427 shutil.rmtree(tmpdir, ignore_errors=True)
420 428
421 429 def expand(ui, repo, *pats, **opts):
422 430 '''expand keywords in the working directory
423 431
424 432 Run after (re)enabling keyword expansion.
425 433
426 434 kwexpand refuses to run if given files contain local changes.
427 435 '''
428 436 # 3rd argument sets expansion to True
429 437 _kwfwrite(ui, repo, True, *pats, **opts)
430 438
431 439 def files(ui, repo, *pats, **opts):
432 440 '''show files configured for keyword expansion
433 441
434 442 List which files in the working directory are matched by the
435 443 [keyword] configuration patterns.
436 444
437 445 Useful to prevent inadvertent keyword expansion and to speed up
438 446 execution by including only files that are actual candidates for
439 447 expansion.
440 448
441 449 See :hg:`help keyword` on how to construct patterns both for
442 450 inclusion and exclusion of files.
443 451
444 452 With -A/--all and -v/--verbose the codes used to show the status
445 453 of files are::
446 454
447 455 K = keyword expansion candidate
448 456 k = keyword expansion candidate (not tracked)
449 457 I = ignored
450 458 i = ignored (not tracked)
451 459 '''
452 460 kwt = kwtools['templater']
453 461 status = _status(ui, repo, kwt, *pats, **opts)
454 462 cwd = pats and repo.getcwd() or ''
455 463 modified, added, removed, deleted, unknown, ignored, clean = status
456 464 files = []
457 465 if not opts.get('unknown') or opts.get('all'):
458 466 files = sorted(modified + added + clean)
459 467 wctx = repo[None]
460 468 kwfiles = kwt.iskwfile(files, wctx)
461 469 kwdeleted = kwt.iskwfile(deleted, wctx)
462 470 kwunknown = kwt.iskwfile(unknown, wctx)
463 471 if not opts.get('ignore') or opts.get('all'):
464 472 showfiles = kwfiles, kwdeleted, kwunknown
465 473 else:
466 474 showfiles = [], [], []
467 475 if opts.get('all') or opts.get('ignore'):
468 476 showfiles += ([f for f in files if f not in kwfiles],
469 477 [f for f in unknown if f not in kwunknown])
470 478 kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
471 479 kwstates = zip('K!kIi', showfiles, kwlabels)
472 480 for char, filenames, kwstate in kwstates:
473 481 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
474 482 for f in filenames:
475 483 ui.write(fmt % repo.pathto(f, cwd), label='kwfiles.' + kwstate)
476 484
477 485 def shrink(ui, repo, *pats, **opts):
478 486 '''revert expanded keywords in the working directory
479 487
480 488 Must be run before changing/disabling active keywords.
481 489
482 490 kwshrink refuses to run if given files contain local changes.
483 491 '''
484 492 # 3rd argument sets expansion to False
485 493 _kwfwrite(ui, repo, False, *pats, **opts)
486 494
487 495
488 496 def uisetup(ui):
489 497 ''' Monkeypatches dispatch._parse to retrieve user command.'''
490 498
491 499 def kwdispatch_parse(orig, ui, args):
492 500 '''Monkeypatch dispatch._parse to obtain running hg command.'''
493 501 cmd, func, args, options, cmdoptions = orig(ui, args)
494 502 kwtools['hgcmd'] = cmd
495 503 return cmd, func, args, options, cmdoptions
496 504
497 505 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
498 506
499 507 def reposetup(ui, repo):
500 508 '''Sets up repo as kwrepo for keyword substitution.
501 509 Overrides file method to return kwfilelog instead of filelog
502 510 if file matches user configuration.
503 511 Wraps commit to overwrite configured files with updated
504 512 keyword substitutions.
505 513 Monkeypatches patch and webcommands.'''
506 514
507 515 try:
508 516 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
509 517 or '.hg' in util.splitpath(repo.root)
510 518 or repo._url.startswith('bundle:')):
511 519 return
512 520 except AttributeError:
513 521 pass
514 522
515 523 inc, exc = [], ['.hg*']
516 524 for pat, opt in ui.configitems('keyword'):
517 525 if opt != 'ignore':
518 526 inc.append(pat)
519 527 else:
520 528 exc.append(pat)
521 529 if not inc:
522 530 return
523 531
524 532 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
525 533
526 534 class kwrepo(repo.__class__):
527 535 def file(self, f):
528 536 if f[0] == '/':
529 537 f = f[1:]
530 538 return kwfilelog(self.sopener, kwt, f)
531 539
532 540 def wread(self, filename):
533 541 data = super(kwrepo, self).wread(filename)
534 542 return kwt.wread(filename, data)
535 543
536 544 def commit(self, *args, **opts):
537 545 # use custom commitctx for user commands
538 546 # other extensions can still wrap repo.commitctx directly
539 547 self.commitctx = self.kwcommitctx
540 548 try:
541 549 return super(kwrepo, self).commit(*args, **opts)
542 550 finally:
543 551 del self.commitctx
544 552
545 553 def kwcommitctx(self, ctx, error=False):
546 554 n = super(kwrepo, self).commitctx(ctx, error)
547 555 # no lock needed, only called from repo.commit() which already locks
548 556 if not kwt.record:
549 557 restrict = kwt.restrict
550 558 kwt.restrict = True
551 559 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
552 560 False, True)
553 561 kwt.restrict = restrict
554 562 return n
555 563
556 564 def rollback(self, dryrun=False):
557 565 wlock = self.wlock()
558 566 try:
559 567 if not dryrun:
560 568 changed = self['.'].files()
561 569 ret = super(kwrepo, self).rollback(dryrun)
562 570 if not dryrun:
563 571 ctx = self['.']
564 572 modified, added = _preselect(self[None].status(), changed)
565 573 kwt.overwrite(ctx, modified, True, True)
566 574 kwt.overwrite(ctx, added, True, False)
567 575 return ret
568 576 finally:
569 577 wlock.release()
570 578
571 579 # monkeypatches
572 580 def kwpatchfile_init(orig, self, ui, fname, opener,
573 581 missing=False, eolmode=None):
574 582 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
575 583 rejects or conflicts due to expanded keywords in working dir.'''
576 584 orig(self, ui, fname, opener, missing, eolmode)
577 585 # shrink keywords read from working dir
578 586 self.lines = kwt.shrinklines(self.fname, self.lines)
579 587
580 588 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
581 589 opts=None, prefix=''):
582 590 '''Monkeypatch patch.diff to avoid expansion.'''
583 591 kwt.restrict = True
584 592 return orig(repo, node1, node2, match, changes, opts, prefix)
585 593
586 594 def kwweb_skip(orig, web, req, tmpl):
587 595 '''Wraps webcommands.x turning off keyword expansion.'''
588 596 kwt.match = util.never
589 597 return orig(web, req, tmpl)
590 598
591 599 def kw_copy(orig, ui, repo, pats, opts, rename=False):
592 600 '''Wraps cmdutil.copy so that copy/rename destinations do not
593 601 contain expanded keywords.
594 602 Note that the source of a regular file destination may also be a
595 603 symlink:
596 604 hg cp sym x -> x is symlink
597 605 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords)
598 606 For the latter we have to follow the symlink to find out whether its
599 607 target is configured for expansion and we therefore must unexpand the
600 608 keywords in the destination.'''
601 609 orig(ui, repo, pats, opts, rename)
602 610 if opts.get('dry_run'):
603 611 return
604 612 wctx = repo[None]
605 613 cwd = repo.getcwd()
606 614
607 615 def haskwsource(dest):
608 616 '''Returns true if dest is a regular file and configured for
609 617 expansion or a symlink which points to a file configured for
610 618 expansion. '''
611 619 source = repo.dirstate.copied(dest)
612 620 if 'l' in wctx.flags(source):
613 621 source = util.canonpath(repo.root, cwd,
614 622 os.path.realpath(source))
615 623 return kwt.match(source)
616 624
617 625 candidates = [f for f in repo.dirstate.copies() if
618 626 not 'l' in wctx.flags(f) and haskwsource(f)]
619 627 kwt.overwrite(wctx, candidates, False, False)
620 628
621 629 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
622 630 '''Wraps record.dorecord expanding keywords after recording.'''
623 631 wlock = repo.wlock()
624 632 try:
625 633 # record returns 0 even when nothing has changed
626 634 # therefore compare nodes before and after
627 635 kwt.record = True
628 636 ctx = repo['.']
629 637 wstatus = repo[None].status()
630 638 ret = orig(ui, repo, commitfunc, *pats, **opts)
631 639 recctx = repo['.']
632 640 if ctx != recctx:
633 641 modified, added = _preselect(wstatus, recctx.files())
634 642 kwt.restrict = False
635 643 kwt.overwrite(recctx, modified, False, True)
636 644 kwt.overwrite(recctx, added, False, True, True)
637 645 kwt.restrict = True
638 646 return ret
639 647 finally:
640 648 wlock.release()
641 649
642 650 def kwfilectx_cmp(orig, self, fctx):
643 651 # keyword affects data size, comparing wdir and filelog size does
644 652 # not make sense
645 653 if (fctx._filerev is None and
646 654 (self._repo._encodefilterpats or
647 655 kwt.match(fctx.path()) and not 'l' in fctx.flags()) or
648 656 self.size() == fctx.size()):
649 657 return self._filelog.cmp(self._filenode, fctx.data())
650 658 return True
651 659
652 660 extensions.wrapfunction(context.filectx, 'cmp', kwfilectx_cmp)
653 661 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
654 662 extensions.wrapfunction(patch, 'diff', kw_diff)
655 663 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
656 664 for c in 'annotate changeset rev filediff diff'.split():
657 665 extensions.wrapfunction(webcommands, c, kwweb_skip)
658 666 for name in recordextensions.split():
659 667 try:
660 668 record = extensions.find(name)
661 669 extensions.wrapfunction(record, 'dorecord', kw_dorecord)
662 670 except KeyError:
663 671 pass
664 672
665 673 repo.__class__ = kwrepo
666 674
667 675 cmdtable = {
668 676 'kwdemo':
669 677 (demo,
670 678 [('d', 'default', None, _('show default keyword template maps')),
671 679 ('f', 'rcfile', '',
672 680 _('read maps from rcfile'), _('FILE'))],
673 681 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
674 682 'kwexpand': (expand, commands.walkopts,
675 683 _('hg kwexpand [OPTION]... [FILE]...')),
676 684 'kwfiles':
677 685 (files,
678 686 [('A', 'all', None, _('show keyword status flags of all files')),
679 687 ('i', 'ignore', None, _('show files excluded from expansion')),
680 688 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
681 689 ] + commands.walkopts,
682 690 _('hg kwfiles [OPTION]... [FILE]...')),
683 691 'kwshrink': (shrink, commands.walkopts,
684 692 _('hg kwshrink [OPTION]... [FILE]...')),
685 693 }
General Comments 0
You need to be logged in to leave comments. Login now