78 Caveat: "hg import" fails if the patch context contains an active |
78 Caveat: "hg import" fails if the patch context contains an active |
79 keyword. In that case run "hg kwshrink", and then reimport. |
79 keyword. In that case run "hg kwshrink", and then reimport. |
80 Or, better, use bundle/unbundle to share changes. |
80 Or, better, use bundle/unbundle to share changes. |
81 ''' |
81 ''' |
82 |
82 |
83 from mercurial import commands, cmdutil, context, fancyopts |
83 from mercurial import commands, cmdutil, context, dispatch |
84 from mercurial import filelog, localrepo, revlog, templater, util |
84 from mercurial import filelog, localrepo, revlog, templater, util |
85 from mercurial.i18n import gettext as _ |
85 from mercurial.i18n import _ |
86 import getopt, os.path, re, shutil, sys, tempfile, time |
86 import re, shutil, sys, tempfile, time |
87 |
|
88 # backwards compatibility hacks |
|
89 |
|
90 try: |
|
91 # cmdutil.parse moves to dispatch._parse in 18a9fbb5cd78 |
|
92 # also avoid name conflict with other dispatch package(s) |
|
93 from mercurial.dispatch import _parse |
|
94 except ImportError: |
|
95 try: |
|
96 # commands.parse moves to cmdutil.parse in 0c61124ad877 |
|
97 _parse = cmdutil.parse |
|
98 except AttributeError: |
|
99 _parse = commands.parse |
|
100 |
|
101 def _pathto(repo, f, cwd=None): |
|
102 '''kwfiles behaves similar to status, using pathto since 78b6add1f966.''' |
|
103 try: |
|
104 return repo.pathto(f, cwd) |
|
105 except AttributeError: |
|
106 return f |
|
107 |
|
108 # commands.parse/cmdutil.parse returned nothing for |
|
109 # "hg diff --rev" before 88803a69b24a due to bug in fancyopts |
|
110 def _fancyopts(args, options, state): |
|
111 '''Fixed fancyopts from 88803a69b24a.''' |
|
112 long = [] |
|
113 short = '' |
|
114 map = {} |
|
115 dt = {} |
|
116 for s, l, d, c in options: |
|
117 pl = l.replace('-', '_') |
|
118 map['-'+s] = map['--'+l] = pl |
|
119 if isinstance(d, list): |
|
120 state[pl] = d[:] |
|
121 else: |
|
122 state[pl] = d |
|
123 dt[pl] = type(d) |
|
124 if (d is not None and d is not True and d is not False and |
|
125 not callable(d)): |
|
126 if s: s += ':' |
|
127 if l: l += '=' |
|
128 if s: short = short + s |
|
129 if l: long.append(l) |
|
130 opts, args = getopt.getopt(args, short, long) |
|
131 for opt, arg in opts: |
|
132 if dt[map[opt]] is type(fancyopts): state[map[opt]](state, map[opt], arg) |
|
133 elif dt[map[opt]] is type(1): state[map[opt]] = int(arg) |
|
134 elif dt[map[opt]] is type(''): state[map[opt]] = arg |
|
135 elif dt[map[opt]] is type([]): state[map[opt]].append(arg) |
|
136 elif dt[map[opt]] is type(None): state[map[opt]] = True |
|
137 elif dt[map[opt]] is type(False): state[map[opt]] = True |
|
138 return args |
|
139 |
|
140 fancyopts.fancyopts = _fancyopts |
|
141 |
|
142 |
87 |
143 commands.optionalrepo += ' kwdemo' |
88 commands.optionalrepo += ' kwdemo' |
144 |
89 |
145 def utcdate(date): |
90 def utcdate(date): |
146 '''Returns hgdate in cvs-like UTC format.''' |
91 '''Returns hgdate in cvs-like UTC format.''' |
177 escaped = map(re.escape, self.templates.keys()) |
122 escaped = map(re.escape, self.templates.keys()) |
178 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped) |
123 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped) |
179 self.re_kw = re.compile(kwpat) |
124 self.re_kw = re.compile(kwpat) |
180 if self.t: |
125 if self.t: |
181 templater.common_filters['utcdate'] = utcdate |
126 templater.common_filters['utcdate'] = utcdate |
182 self.t = self._changeset_templater() |
127 self.t = cmdutil.changeset_templater(self.ui, self.repo, |
183 |
128 False, '', False) |
184 def _changeset_templater(self): |
|
185 '''Backwards compatible cmdutil.changeset_templater.''' |
|
186 # before 1e0b94cfba0e there was an extra "brinfo" argument |
|
187 try: |
|
188 return cmdutil.changeset_templater(self.ui, self.repo, |
|
189 False, '', False) |
|
190 except TypeError: |
|
191 return cmdutil.changeset_templater(self.ui, self.repo, |
|
192 False, None, '', False) |
|
193 |
|
194 def _wwrite(self, f, data, man): |
|
195 '''Makes repo.wwrite backwards compatible.''' |
|
196 # 656e06eebda7 removed file descriptor argument |
|
197 # 67982d3ee76c added flags argument |
|
198 try: |
|
199 self.repo.wwrite(f, data, man.flags(f)) |
|
200 except (AttributeError, TypeError): |
|
201 self.repo.wwrite(f, data) |
|
202 |
|
203 def _normal(self, files): |
|
204 '''Backwards compatible repo.dirstate.normal/update.''' |
|
205 # 6fd953d5faea introduced dirstate.normal() |
|
206 try: |
|
207 for f in files: |
|
208 self.repo.dirstate.normal(f) |
|
209 except AttributeError: |
|
210 self.repo.dirstate.update(files, 'n') |
|
211 |
129 |
212 def kwsub(self, mobj): |
130 def kwsub(self, mobj): |
213 '''Substitutes keyword using corresponding template.''' |
131 '''Substitutes keyword using corresponding template.''' |
214 kw = mobj.group(1) |
132 kw = mobj.group(1) |
215 self.t.use_template(self.templates[kw]) |
133 self.t.use_template(self.templates[kw]) |
236 return data |
154 return data |
237 return self.substitute(node, data, self.re_kw.sub) |
155 return self.substitute(node, data, self.re_kw.sub) |
238 |
156 |
239 def process(self, node, data): |
157 def process(self, node, data): |
240 '''Returns a tuple: data, count. |
158 '''Returns a tuple: data, count. |
241 Count is number of keywords/keyword substitutions. |
159 Count is number of keywords/keyword substitutions, indicates |
|
160 to caller whether to act on file containing data. |
242 Keywords in data are expanded, if templater was initialized.''' |
161 Keywords in data are expanded, if templater was initialized.''' |
243 if util.binary(data): |
162 if util.binary(data): |
244 return data, None |
163 return data, None |
245 if self.t: |
164 if self.t: |
246 return self.substitute(node, data, self.re_kw.subn) |
165 return self.substitute(node, data, self.re_kw.subn) |
257 Keywords are expanded if keyword templater is initialized, |
176 Keywords are expanded if keyword templater is initialized, |
258 otherwise their substitution is removed.''' |
177 otherwise their substitution is removed.''' |
259 expand = self.t is not None |
178 expand = self.t is not None |
260 action = ('shrinking', 'expanding')[expand] |
179 action = ('shrinking', 'expanding')[expand] |
261 notify = (self.ui.note, self.ui.debug)[commit] |
180 notify = (self.ui.note, self.ui.debug)[commit] |
262 overwritten = [] |
|
263 for f in candidates: |
181 for f in candidates: |
264 fp = self.repo.file(f, kwexp=expand, kwmatch=True) |
182 fp = self.repo.file(f, kwexp=expand, kwmatch=True) |
265 data, kwfound = fp.kwctread(man[f]) |
183 data, kwfound = fp.kwctread(man[f]) |
266 if kwfound: |
184 if kwfound: |
267 notify(_('overwriting %s %s keywords\n') % (f, action)) |
185 notify(_('overwriting %s %s keywords\n') % (f, action)) |
268 self._wwrite(f, data, man) |
186 self.repo.wwrite(f, data, man.flags(f)) |
269 overwritten.append(f) |
187 self.repo.dirstate.normal(f) |
270 self._normal(overwritten) |
|
271 |
188 |
272 class kwfilelog(filelog.filelog): |
189 class kwfilelog(filelog.filelog): |
273 ''' |
190 ''' |
274 Subclass of filelog to hook into its read, add, cmp methods. |
191 Subclass of filelog to hook into its read, add, cmp methods. |
275 Keywords are "stored" unexpanded, and processed on reading. |
192 Keywords are "stored" unexpanded, and processed on reading. |
301 t2 = super(kwfilelog, self).read(node) |
218 t2 = super(kwfilelog, self).read(node) |
302 return t2 != text |
219 return t2 != text |
303 return revlog.revlog.cmp(self, node, text) |
220 return revlog.revlog.cmp(self, node, text) |
304 |
221 |
305 def _status(ui, repo, *pats, **opts): |
222 def _status(ui, repo, *pats, **opts): |
|
223 '''Bails out if [keyword] configuration is not active. |
|
224 Returns status of working directory.''' |
306 if hasattr(ui, 'kwfmatcher'): |
225 if hasattr(ui, 'kwfmatcher'): |
307 files, match, anypats = cmdutil.matchpats(repo, pats, opts) |
226 files, match, anypats = cmdutil.matchpats(repo, pats, opts) |
308 return repo.status(files=files, match=match, list_clean=True) |
227 return repo.status(files=files, match=match, list_clean=True) |
309 if ui.configitems('keyword'): |
228 if ui.configitems('keyword'): |
310 raise util.Abort(_('[keyword] patterns cannot match')) |
229 raise util.Abort(_('[keyword] patterns cannot match')) |
438 if opts['untracked']: |
357 if opts['untracked']: |
439 files = modified + added + unknown + clean |
358 files = modified + added + unknown + clean |
440 else: |
359 else: |
441 files = modified + added + clean |
360 files = modified + added + clean |
442 files.sort() |
361 files.sort() |
443 # use the full definition of repo._link for backwards compatibility |
362 kwfiles = [f for f in files if ui.kwfmatcher(f) and not repo._link(f)] |
444 kwfiles = [f for f in files if ui.kwfmatcher(f) |
|
445 and not os.path.islink(repo.wjoin(f))] |
|
446 cwd = pats and repo.getcwd() or '' |
363 cwd = pats and repo.getcwd() or '' |
447 allf = opts['all'] |
364 allf = opts['all'] |
448 ignore = opts['ignore'] |
365 ignore = opts['ignore'] |
449 flag = (allf or ui.verbose) and 1 or 0 |
366 flag = (allf or ui.verbose) and 1 or 0 |
450 if not ignore: |
367 if not ignore: |
451 format = ('%s\n', 'K %s\n')[flag] |
368 format = ('%s\n', 'K %s\n')[flag] |
452 for k in kwfiles: |
369 for k in kwfiles: |
453 ui.write(format % _pathto(repo, k, cwd)) |
370 ui.write(format % repo.pathto(k, cwd)) |
454 if allf or ignore: |
371 if allf or ignore: |
455 format = ('%s\n', 'I %s\n')[flag] |
372 format = ('%s\n', 'I %s\n')[flag] |
456 for i in [f for f in files if f not in kwfiles]: |
373 for i in [f for f in files if f not in kwfiles]: |
457 ui.write(format % _pathto(repo, i, cwd)) |
374 ui.write(format % repo.pathto(i, cwd)) |
458 |
375 |
459 def shrink(ui, repo, *pats, **opts): |
376 def shrink(ui, repo, *pats, **opts): |
460 '''revert expanded keywords in working directory |
377 '''revert expanded keywords in working directory |
461 |
378 |
462 Run before changing/disabling active keywords |
379 Run before changing/disabling active keywords |
479 |
396 |
480 nokwcommands = ['add', 'addremove', 'bundle', 'clone', 'copy', 'export', |
397 nokwcommands = ['add', 'addremove', 'bundle', 'clone', 'copy', 'export', |
481 'grep', 'identify', 'incoming', 'init', 'outgoing', 'push', |
398 'grep', 'identify', 'incoming', 'init', 'outgoing', 'push', |
482 'remove', 'rename', 'rollback', 'convert'] |
399 'remove', 'rename', 'rollback', 'convert'] |
483 |
400 |
484 if not repo.local() or _parse(ui, sys.argv[1:])[0] in nokwcommands: |
401 if (not repo.local() or |
|
402 dispatch._parse(ui, sys.argv[1:])[0] in nokwcommands): |
485 return |
403 return |
486 |
404 |
487 inc, exc = [], ['.hgtags'] |
405 inc, exc = [], ['.hgtags'] |
488 for pat, opt in ui.configitems('keyword'): |
406 for pat, opt in ui.configitems('keyword'): |
489 if opt != 'ignore': |
407 if opt != 'ignore': |
502 if kwmatch or ui.kwfmatcher(f): |
420 if kwmatch or ui.kwfmatcher(f): |
503 kwt = kwtemplater(ui, self, kwexp, path=f) |
421 kwt = kwtemplater(ui, self, kwexp, path=f) |
504 return kwfilelog(self.sopener, f, kwt) |
422 return kwfilelog(self.sopener, f, kwt) |
505 return filelog.filelog(self.sopener, f) |
423 return filelog.filelog(self.sopener, f) |
506 |
424 |
507 def _commit(self, files, text, user, date, match, force, lock, wlock, |
425 def commit(self, files=None, text='', user=None, date=None, |
508 force_editor, p1, p2, extra): |
426 match=util.always, force=False, force_editor=False, |
509 '''Private commit wrapper for backwards compatibility.''' |
427 p1=None, p2=None, extra={}): |
|
428 wlock = lock = None |
510 try: |
429 try: |
511 return super(kwrepo, self).commit(files=files, text=text, |
430 wlock = self.wlock() |
512 user=user, date=date, |
431 lock = self.lock() |
513 match=match, force=force, |
432 node = super(kwrepo, |
514 lock=lock, wlock=wlock, |
433 self).commit(files=files, text=text, user=user, |
515 force_editor=force_editor, |
434 date=date, match=match, force=force, |
516 p1=p1, p2=p2, extra=extra) |
435 force_editor=force_editor, |
517 except TypeError: |
436 p1=p1, p2=p2, extra=extra) |
518 return super(kwrepo, self).commit(files=files, text=text, |
|
519 user=user, date=date, |
|
520 match=match, force=force, |
|
521 force_editor=force_editor, |
|
522 p1=p1, p2=p2, extra=extra) |
|
523 |
|
524 def commit(self, files=None, text='', user=None, date=None, |
|
525 match=util.always, force=False, lock=None, wlock=None, |
|
526 force_editor=False, p1=None, p2=None, extra={}): |
|
527 # (w)lock arguments removed in 126f527b3ba3 |
|
528 # so they are None or what was passed to commit |
|
529 # use private _(w)lock for deletion |
|
530 _lock = lock |
|
531 _wlock = wlock |
|
532 del wlock, lock |
|
533 try: |
|
534 if not _wlock: |
|
535 _wlock = self.wlock() |
|
536 if not _lock: |
|
537 _lock = self.lock() |
|
538 node = self._commit(files, text, user, date, match, force, |
|
539 _lock, _wlock, force_editor, p1, p2, extra) |
|
540 if node is not None: |
437 if node is not None: |
541 cl = self.changelog.read(node) |
438 cl = self.changelog.read(node) |
542 mn = self.manifest.read(cl[0]) |
439 mn = self.manifest.read(cl[0]) |
543 candidates = [f for f in cl[3] if mn.has_key(f) |
440 candidates = [f for f in cl[3] if mn.has_key(f) |
544 and _iskwfile(ui, mn, f)] |
441 and _iskwfile(ui, mn, f)] |
545 if candidates: |
442 if candidates: |
546 # 6th, 7th arguments set expansion, commit to True |
443 # 6th, 7th arguments set expansion, commit to True |
547 _overwrite(ui, self, candidates, node, mn, True, True) |
444 _overwrite(ui, self, candidates, node, mn, True, True) |
548 return node |
445 return node |
549 finally: |
446 finally: |
550 del _wlock, _lock |
447 del wlock, lock |
551 |
448 |
552 repo.__class__ = kwrepo |
449 repo.__class__ = kwrepo |
553 |
450 |
554 |
451 |
555 cmdtable = { |
452 cmdtable = { |