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