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