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