##// END OF EJS Templates
keyword: mark improved demo commit message for translation...
Christian Ebert -
r10499:4401b0df stable
parent child Browse files
Show More
@@ -1,550 +1,550
1 # keyword.py - $Keyword$ expansion for Mercurial
1 # keyword.py - $Keyword$ expansion for Mercurial
2 #
2 #
3 # Copyright 2007-2009 Christian Ebert <blacktrash@gmx.net>
3 # Copyright 2007-2009 Christian Ebert <blacktrash@gmx.net>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 #
7 #
8 # $Id$
8 # $Id$
9 #
9 #
10 # Keyword expansion hack against the grain of a DSCM
10 # Keyword expansion hack against the grain of a DSCM
11 #
11 #
12 # There are many good reasons why this is not needed in a distributed
12 # There are many good reasons why this is not needed in a distributed
13 # SCM, still it may be useful in very small projects based on single
13 # SCM, still it may be useful in very small projects based on single
14 # files (like LaTeX packages), that are mostly addressed to an
14 # files (like LaTeX packages), that are mostly addressed to an
15 # audience not running a version control system.
15 # audience not running a version control system.
16 #
16 #
17 # For in-depth discussion refer to
17 # For in-depth discussion refer to
18 # <http://mercurial.selenic.com/wiki/KeywordPlan>.
18 # <http://mercurial.selenic.com/wiki/KeywordPlan>.
19 #
19 #
20 # Keyword expansion is based on Mercurial's changeset template mappings.
20 # Keyword expansion is based on Mercurial's changeset template mappings.
21 #
21 #
22 # Binary files are not touched.
22 # Binary files are not touched.
23 #
23 #
24 # Files to act upon/ignore are specified in the [keyword] section.
24 # Files to act upon/ignore are specified in the [keyword] section.
25 # Customized keyword template mappings in the [keywordmaps] section.
25 # Customized keyword template mappings in the [keywordmaps] section.
26 #
26 #
27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
28
28
29 '''expand keywords in tracked files
29 '''expand keywords in tracked files
30
30
31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
32 tracked text files selected by your configuration.
32 tracked text files selected by your configuration.
33
33
34 Keywords are only expanded in local repositories and not stored in the
34 Keywords are only expanded in local repositories and not stored in the
35 change history. The mechanism can be regarded as a convenience for the
35 change history. The mechanism can be regarded as a convenience for the
36 current user or for archive distribution.
36 current user or for archive distribution.
37
37
38 Configuration is done in the [keyword] and [keywordmaps] sections of
38 Configuration is done in the [keyword] and [keywordmaps] sections of
39 hgrc files.
39 hgrc files.
40
40
41 Example::
41 Example::
42
42
43 [keyword]
43 [keyword]
44 # expand keywords in every python file except those matching "x*"
44 # expand keywords in every python file except those matching "x*"
45 **.py =
45 **.py =
46 x* = ignore
46 x* = ignore
47
47
48 NOTE: the more specific you are in your filename patterns the less you
48 NOTE: the more specific you are in your filename patterns the less you
49 lose speed in huge repositories.
49 lose speed in huge repositories.
50
50
51 For [keywordmaps] template mapping and expansion demonstration and
51 For [keywordmaps] template mapping and expansion demonstration and
52 control run "hg kwdemo". See "hg help templates" for a list of
52 control run "hg kwdemo". See "hg help templates" for a list of
53 available templates and filters.
53 available templates and filters.
54
54
55 An additional date template filter {date|utcdate} is provided. It
55 An additional date template filter {date|utcdate} is provided. It
56 returns a date like "2006/09/18 15:13:13".
56 returns a date like "2006/09/18 15:13:13".
57
57
58 The default template mappings (view with "hg kwdemo -d") can be
58 The default template mappings (view with "hg kwdemo -d") can be
59 replaced with customized keywords and templates. Again, run "hg
59 replaced with customized keywords and templates. Again, run "hg
60 kwdemo" to control the results of your config changes.
60 kwdemo" to control the results of your config changes.
61
61
62 Before changing/disabling active keywords, run "hg kwshrink" to avoid
62 Before changing/disabling active keywords, run "hg kwshrink" to avoid
63 the risk of inadvertently storing expanded keywords in the change
63 the risk of inadvertently storing expanded keywords in the change
64 history.
64 history.
65
65
66 To force expansion after enabling it, or a configuration change, run
66 To force expansion after enabling it, or a configuration change, run
67 "hg kwexpand".
67 "hg kwexpand".
68
68
69 Also, when committing with the record extension or using mq's qrecord,
69 Also, when committing with the record extension or using mq's qrecord,
70 be aware that keywords cannot be updated. Again, run "hg kwexpand" on
70 be aware that keywords cannot be updated. Again, run "hg kwexpand" on
71 the files in question to update keyword expansions after all changes
71 the files in question to update keyword expansions after all changes
72 have been checked in.
72 have been checked in.
73
73
74 Expansions spanning more than one line and incremental expansions,
74 Expansions spanning more than one line and incremental expansions,
75 like CVS' $Log$, are not supported. A keyword template map "Log =
75 like CVS' $Log$, are not supported. A keyword template map "Log =
76 {desc}" expands to the first line of the changeset description.
76 {desc}" expands to the first line of the changeset description.
77 '''
77 '''
78
78
79 from mercurial import commands, cmdutil, dispatch, filelog, revlog, extensions
79 from mercurial import commands, cmdutil, dispatch, filelog, revlog, extensions
80 from mercurial import patch, localrepo, templater, templatefilters, util, match
80 from mercurial import patch, localrepo, templater, templatefilters, util, match
81 from mercurial.hgweb import webcommands
81 from mercurial.hgweb import webcommands
82 from mercurial.lock import release
82 from mercurial.lock import release
83 from mercurial.node import nullid
83 from mercurial.node import nullid
84 from mercurial.i18n import _
84 from mercurial.i18n import _
85 import re, shutil, tempfile
85 import re, shutil, tempfile
86
86
87 commands.optionalrepo += ' kwdemo'
87 commands.optionalrepo += ' kwdemo'
88
88
89 # hg commands that do not act on keywords
89 # hg commands that do not act on keywords
90 nokwcommands = ('add addremove annotate bundle copy export grep incoming init'
90 nokwcommands = ('add addremove annotate bundle copy export grep incoming init'
91 ' log outgoing push rename rollback tip verify'
91 ' log outgoing push rename rollback tip verify'
92 ' convert email glog')
92 ' convert email glog')
93
93
94 # hg commands that trigger expansion only when writing to working dir,
94 # hg commands that trigger expansion only when writing to working dir,
95 # not when reading filelog, and unexpand when reading from working dir
95 # not when reading filelog, and unexpand when reading from working dir
96 restricted = ('merge record resolve qfold qimport qnew qpush qrefresh qrecord'
96 restricted = ('merge record resolve qfold qimport qnew qpush qrefresh qrecord'
97 ' transplant')
97 ' transplant')
98
98
99 # provide cvs-like UTC date filter
99 # provide cvs-like UTC date filter
100 utcdate = lambda x: util.datestr((x[0], 0), '%Y/%m/%d %H:%M:%S')
100 utcdate = lambda x: util.datestr((x[0], 0), '%Y/%m/%d %H:%M:%S')
101
101
102 # make keyword tools accessible
102 # make keyword tools accessible
103 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
103 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
104
104
105
105
106 class kwtemplater(object):
106 class kwtemplater(object):
107 '''
107 '''
108 Sets up keyword templates, corresponding keyword regex, and
108 Sets up keyword templates, corresponding keyword regex, and
109 provides keyword substitution functions.
109 provides keyword substitution functions.
110 '''
110 '''
111 templates = {
111 templates = {
112 'Revision': '{node|short}',
112 'Revision': '{node|short}',
113 'Author': '{author|user}',
113 'Author': '{author|user}',
114 'Date': '{date|utcdate}',
114 'Date': '{date|utcdate}',
115 'RCSfile': '{file|basename},v',
115 'RCSfile': '{file|basename},v',
116 'RCSFile': '{file|basename},v', # kept for backwards compatibility
116 'RCSFile': '{file|basename},v', # kept for backwards compatibility
117 # with hg-keyword
117 # with hg-keyword
118 'Source': '{root}/{file},v',
118 'Source': '{root}/{file},v',
119 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
119 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
120 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
120 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
121 }
121 }
122
122
123 def __init__(self, ui, repo):
123 def __init__(self, ui, repo):
124 self.ui = ui
124 self.ui = ui
125 self.repo = repo
125 self.repo = repo
126 self.match = match.match(repo.root, '', [],
126 self.match = match.match(repo.root, '', [],
127 kwtools['inc'], kwtools['exc'])
127 kwtools['inc'], kwtools['exc'])
128 self.restrict = kwtools['hgcmd'] in restricted.split()
128 self.restrict = kwtools['hgcmd'] in restricted.split()
129
129
130 kwmaps = self.ui.configitems('keywordmaps')
130 kwmaps = self.ui.configitems('keywordmaps')
131 if kwmaps: # override default templates
131 if kwmaps: # override default templates
132 self.templates = dict((k, templater.parsestring(v, False))
132 self.templates = dict((k, templater.parsestring(v, False))
133 for k, v in kwmaps)
133 for k, v in kwmaps)
134 escaped = map(re.escape, self.templates.keys())
134 escaped = map(re.escape, self.templates.keys())
135 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
135 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
136 self.re_kw = re.compile(kwpat)
136 self.re_kw = re.compile(kwpat)
137
137
138 templatefilters.filters['utcdate'] = utcdate
138 templatefilters.filters['utcdate'] = utcdate
139 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
139 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
140 False, None, '', False)
140 False, None, '', False)
141
141
142 def substitute(self, data, path, ctx, subfunc):
142 def substitute(self, data, path, ctx, subfunc):
143 '''Replaces keywords in data with expanded template.'''
143 '''Replaces keywords in data with expanded template.'''
144 def kwsub(mobj):
144 def kwsub(mobj):
145 kw = mobj.group(1)
145 kw = mobj.group(1)
146 self.ct.use_template(self.templates[kw])
146 self.ct.use_template(self.templates[kw])
147 self.ui.pushbuffer()
147 self.ui.pushbuffer()
148 self.ct.show(ctx, root=self.repo.root, file=path)
148 self.ct.show(ctx, root=self.repo.root, file=path)
149 ekw = templatefilters.firstline(self.ui.popbuffer())
149 ekw = templatefilters.firstline(self.ui.popbuffer())
150 return '$%s: %s $' % (kw, ekw)
150 return '$%s: %s $' % (kw, ekw)
151 return subfunc(kwsub, data)
151 return subfunc(kwsub, data)
152
152
153 def expand(self, path, node, data):
153 def expand(self, path, node, data):
154 '''Returns data with keywords expanded.'''
154 '''Returns data with keywords expanded.'''
155 if not self.restrict and self.match(path) and not util.binary(data):
155 if not self.restrict and self.match(path) and not util.binary(data):
156 ctx = self.repo.filectx(path, fileid=node).changectx()
156 ctx = self.repo.filectx(path, fileid=node).changectx()
157 return self.substitute(data, path, ctx, self.re_kw.sub)
157 return self.substitute(data, path, ctx, self.re_kw.sub)
158 return data
158 return data
159
159
160 def iskwfile(self, path, flagfunc):
160 def iskwfile(self, path, flagfunc):
161 '''Returns true if path matches [keyword] pattern
161 '''Returns true if path matches [keyword] pattern
162 and is not a symbolic link.
162 and is not a symbolic link.
163 Caveat: localrepository._link fails on Windows.'''
163 Caveat: localrepository._link fails on Windows.'''
164 return self.match(path) and not 'l' in flagfunc(path)
164 return self.match(path) and not 'l' in flagfunc(path)
165
165
166 def overwrite(self, node, expand, files):
166 def overwrite(self, node, expand, files):
167 '''Overwrites selected files expanding/shrinking keywords.'''
167 '''Overwrites selected files expanding/shrinking keywords.'''
168 ctx = self.repo[node]
168 ctx = self.repo[node]
169 mf = ctx.manifest()
169 mf = ctx.manifest()
170 if node is not None: # commit
170 if node is not None: # commit
171 files = [f for f in ctx.files() if f in mf]
171 files = [f for f in ctx.files() if f in mf]
172 notify = self.ui.debug
172 notify = self.ui.debug
173 else: # kwexpand/kwshrink
173 else: # kwexpand/kwshrink
174 notify = self.ui.note
174 notify = self.ui.note
175 candidates = [f for f in files if self.iskwfile(f, ctx.flags)]
175 candidates = [f for f in files if self.iskwfile(f, ctx.flags)]
176 if candidates:
176 if candidates:
177 self.restrict = True # do not expand when reading
177 self.restrict = True # do not expand when reading
178 msg = (expand and _('overwriting %s expanding keywords\n')
178 msg = (expand and _('overwriting %s expanding keywords\n')
179 or _('overwriting %s shrinking keywords\n'))
179 or _('overwriting %s shrinking keywords\n'))
180 for f in candidates:
180 for f in candidates:
181 fp = self.repo.file(f)
181 fp = self.repo.file(f)
182 data = fp.read(mf[f])
182 data = fp.read(mf[f])
183 if util.binary(data):
183 if util.binary(data):
184 continue
184 continue
185 if expand:
185 if expand:
186 if node is None:
186 if node is None:
187 ctx = self.repo.filectx(f, fileid=mf[f]).changectx()
187 ctx = self.repo.filectx(f, fileid=mf[f]).changectx()
188 data, found = self.substitute(data, f, ctx,
188 data, found = self.substitute(data, f, ctx,
189 self.re_kw.subn)
189 self.re_kw.subn)
190 else:
190 else:
191 found = self.re_kw.search(data)
191 found = self.re_kw.search(data)
192 if found:
192 if found:
193 notify(msg % f)
193 notify(msg % f)
194 self.repo.wwrite(f, data, mf.flags(f))
194 self.repo.wwrite(f, data, mf.flags(f))
195 if node is None:
195 if node is None:
196 self.repo.dirstate.normal(f)
196 self.repo.dirstate.normal(f)
197 self.restrict = False
197 self.restrict = False
198
198
199 def shrinktext(self, text):
199 def shrinktext(self, text):
200 '''Unconditionally removes all keyword substitutions from text.'''
200 '''Unconditionally removes all keyword substitutions from text.'''
201 return self.re_kw.sub(r'$\1$', text)
201 return self.re_kw.sub(r'$\1$', text)
202
202
203 def shrink(self, fname, text):
203 def shrink(self, fname, text):
204 '''Returns text with all keyword substitutions removed.'''
204 '''Returns text with all keyword substitutions removed.'''
205 if self.match(fname) and not util.binary(text):
205 if self.match(fname) and not util.binary(text):
206 return self.shrinktext(text)
206 return self.shrinktext(text)
207 return text
207 return text
208
208
209 def shrinklines(self, fname, lines):
209 def shrinklines(self, fname, lines):
210 '''Returns lines with keyword substitutions removed.'''
210 '''Returns lines with keyword substitutions removed.'''
211 if self.match(fname):
211 if self.match(fname):
212 text = ''.join(lines)
212 text = ''.join(lines)
213 if not util.binary(text):
213 if not util.binary(text):
214 return self.shrinktext(text).splitlines(True)
214 return self.shrinktext(text).splitlines(True)
215 return lines
215 return lines
216
216
217 def wread(self, fname, data):
217 def wread(self, fname, data):
218 '''If in restricted mode returns data read from wdir with
218 '''If in restricted mode returns data read from wdir with
219 keyword substitutions removed.'''
219 keyword substitutions removed.'''
220 return self.restrict and self.shrink(fname, data) or data
220 return self.restrict and self.shrink(fname, data) or data
221
221
222 class kwfilelog(filelog.filelog):
222 class kwfilelog(filelog.filelog):
223 '''
223 '''
224 Subclass of filelog to hook into its read, add, cmp methods.
224 Subclass of filelog to hook into its read, add, cmp methods.
225 Keywords are "stored" unexpanded, and processed on reading.
225 Keywords are "stored" unexpanded, and processed on reading.
226 '''
226 '''
227 def __init__(self, opener, kwt, path):
227 def __init__(self, opener, kwt, path):
228 super(kwfilelog, self).__init__(opener, path)
228 super(kwfilelog, self).__init__(opener, path)
229 self.kwt = kwt
229 self.kwt = kwt
230 self.path = path
230 self.path = path
231
231
232 def read(self, node):
232 def read(self, node):
233 '''Expands keywords when reading filelog.'''
233 '''Expands keywords when reading filelog.'''
234 data = super(kwfilelog, self).read(node)
234 data = super(kwfilelog, self).read(node)
235 return self.kwt.expand(self.path, node, data)
235 return self.kwt.expand(self.path, node, data)
236
236
237 def add(self, text, meta, tr, link, p1=None, p2=None):
237 def add(self, text, meta, tr, link, p1=None, p2=None):
238 '''Removes keyword substitutions when adding to filelog.'''
238 '''Removes keyword substitutions when adding to filelog.'''
239 text = self.kwt.shrink(self.path, text)
239 text = self.kwt.shrink(self.path, text)
240 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
240 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
241
241
242 def cmp(self, node, text):
242 def cmp(self, node, text):
243 '''Removes keyword substitutions for comparison.'''
243 '''Removes keyword substitutions for comparison.'''
244 text = self.kwt.shrink(self.path, text)
244 text = self.kwt.shrink(self.path, text)
245 if self.renamed(node):
245 if self.renamed(node):
246 t2 = super(kwfilelog, self).read(node)
246 t2 = super(kwfilelog, self).read(node)
247 return t2 != text
247 return t2 != text
248 return revlog.revlog.cmp(self, node, text)
248 return revlog.revlog.cmp(self, node, text)
249
249
250 def _status(ui, repo, kwt, *pats, **opts):
250 def _status(ui, repo, kwt, *pats, **opts):
251 '''Bails out if [keyword] configuration is not active.
251 '''Bails out if [keyword] configuration is not active.
252 Returns status of working directory.'''
252 Returns status of working directory.'''
253 if kwt:
253 if kwt:
254 unknown = (opts.get('unknown') or opts.get('all')
254 unknown = (opts.get('unknown') or opts.get('all')
255 or opts.get('untracked'))
255 or opts.get('untracked'))
256 return repo.status(match=cmdutil.match(repo, pats, opts), clean=True,
256 return repo.status(match=cmdutil.match(repo, pats, opts), clean=True,
257 unknown=unknown)
257 unknown=unknown)
258 if ui.configitems('keyword'):
258 if ui.configitems('keyword'):
259 raise util.Abort(_('[keyword] patterns cannot match'))
259 raise util.Abort(_('[keyword] patterns cannot match'))
260 raise util.Abort(_('no [keyword] patterns configured'))
260 raise util.Abort(_('no [keyword] patterns configured'))
261
261
262 def _kwfwrite(ui, repo, expand, *pats, **opts):
262 def _kwfwrite(ui, repo, expand, *pats, **opts):
263 '''Selects files and passes them to kwtemplater.overwrite.'''
263 '''Selects files and passes them to kwtemplater.overwrite.'''
264 if repo.dirstate.parents()[1] != nullid:
264 if repo.dirstate.parents()[1] != nullid:
265 raise util.Abort(_('outstanding uncommitted merge'))
265 raise util.Abort(_('outstanding uncommitted merge'))
266 kwt = kwtools['templater']
266 kwt = kwtools['templater']
267 status = _status(ui, repo, kwt, *pats, **opts)
267 status = _status(ui, repo, kwt, *pats, **opts)
268 modified, added, removed, deleted = status[:4]
268 modified, added, removed, deleted = status[:4]
269 if modified or added or removed or deleted:
269 if modified or added or removed or deleted:
270 raise util.Abort(_('outstanding uncommitted changes'))
270 raise util.Abort(_('outstanding uncommitted changes'))
271 wlock = lock = None
271 wlock = lock = None
272 try:
272 try:
273 wlock = repo.wlock()
273 wlock = repo.wlock()
274 lock = repo.lock()
274 lock = repo.lock()
275 kwt.overwrite(None, expand, status[6])
275 kwt.overwrite(None, expand, status[6])
276 finally:
276 finally:
277 release(lock, wlock)
277 release(lock, wlock)
278
278
279 def demo(ui, repo, *args, **opts):
279 def demo(ui, repo, *args, **opts):
280 '''print [keywordmaps] configuration and an expansion example
280 '''print [keywordmaps] configuration and an expansion example
281
281
282 Show current, custom, or default keyword template maps and their
282 Show current, custom, or default keyword template maps and their
283 expansions.
283 expansions.
284
284
285 Extend the current configuration by specifying maps as arguments
285 Extend the current configuration by specifying maps as arguments
286 and using -f/--rcfile to source an external hgrc file.
286 and using -f/--rcfile to source an external hgrc file.
287
287
288 Use -d/--default to disable current configuration.
288 Use -d/--default to disable current configuration.
289
289
290 See "hg help templates" for information on templates and filters.
290 See "hg help templates" for information on templates and filters.
291 '''
291 '''
292 def demoitems(section, items):
292 def demoitems(section, items):
293 ui.write('[%s]\n' % section)
293 ui.write('[%s]\n' % section)
294 for k, v in sorted(items):
294 for k, v in sorted(items):
295 ui.write('%s = %s\n' % (k, v))
295 ui.write('%s = %s\n' % (k, v))
296
296
297 msg = 'hg keyword config and expansion example'
298 fn = 'demo.txt'
297 fn = 'demo.txt'
299 branchname = 'demobranch'
298 branchname = 'demobranch'
300 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
299 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
301 ui.note(_('creating temporary repository at %s\n') % tmpdir)
300 ui.note(_('creating temporary repository at %s\n') % tmpdir)
302 repo = localrepo.localrepository(ui, tmpdir, True)
301 repo = localrepo.localrepository(ui, tmpdir, True)
303 ui.setconfig('keyword', fn, '')
302 ui.setconfig('keyword', fn, '')
304
303
305 uikwmaps = ui.configitems('keywordmaps')
304 uikwmaps = ui.configitems('keywordmaps')
306 if args or opts.get('rcfile'):
305 if args or opts.get('rcfile'):
307 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
306 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
308 if uikwmaps:
307 if uikwmaps:
309 ui.status(_('\textending current template maps\n'))
308 ui.status(_('\textending current template maps\n'))
310 if opts.get('default') or not uikwmaps:
309 if opts.get('default') or not uikwmaps:
311 ui.status(_('\toverriding default template maps\n'))
310 ui.status(_('\toverriding default template maps\n'))
312 if opts.get('rcfile'):
311 if opts.get('rcfile'):
313 ui.readconfig(opts.get('rcfile'))
312 ui.readconfig(opts.get('rcfile'))
314 if args:
313 if args:
315 # simulate hgrc parsing
314 # simulate hgrc parsing
316 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
315 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
317 fp = repo.opener('hgrc', 'w')
316 fp = repo.opener('hgrc', 'w')
318 fp.writelines(rcmaps)
317 fp.writelines(rcmaps)
319 fp.close()
318 fp.close()
320 ui.readconfig(repo.join('hgrc'))
319 ui.readconfig(repo.join('hgrc'))
321 kwmaps = dict(ui.configitems('keywordmaps'))
320 kwmaps = dict(ui.configitems('keywordmaps'))
322 elif opts.get('default'):
321 elif opts.get('default'):
323 ui.status(_('\n\tconfiguration using default keyword template maps\n'))
322 ui.status(_('\n\tconfiguration using default keyword template maps\n'))
324 kwmaps = kwtemplater.templates
323 kwmaps = kwtemplater.templates
325 if uikwmaps:
324 if uikwmaps:
326 ui.status(_('\tdisabling current template maps\n'))
325 ui.status(_('\tdisabling current template maps\n'))
327 for k, v in kwmaps.iteritems():
326 for k, v in kwmaps.iteritems():
328 ui.setconfig('keywordmaps', k, v)
327 ui.setconfig('keywordmaps', k, v)
329 else:
328 else:
330 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
329 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
331 kwmaps = dict(uikwmaps) or kwtemplater.templates
330 kwmaps = dict(uikwmaps) or kwtemplater.templates
332
331
333 uisetup(ui)
332 uisetup(ui)
334 reposetup(ui, repo)
333 reposetup(ui, repo)
335 for k, v in ui.configitems('extensions'):
334 for k, v in ui.configitems('extensions'):
336 if k.endswith('keyword'):
335 if k.endswith('keyword'):
337 extension = '%s = %s' % (k, v)
336 extension = '%s = %s' % (k, v)
338 break
337 break
339 ui.write('[extensions]\n%s\n' % extension)
338 ui.write('[extensions]\n%s\n' % extension)
340 demoitems('keyword', ui.configitems('keyword'))
339 demoitems('keyword', ui.configitems('keyword'))
341 demoitems('keywordmaps', kwmaps.iteritems())
340 demoitems('keywordmaps', kwmaps.iteritems())
342 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
341 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
343 repo.wopener(fn, 'w').write(keywords)
342 repo.wopener(fn, 'w').write(keywords)
344 repo.add([fn])
343 repo.add([fn])
345 path = repo.wjoin(fn)
344 path = repo.wjoin(fn)
346 ui.note(_('\nkeywords written to %s:\n') % path)
345 ui.note(_('\nkeywords written to %s:\n') % path)
347 ui.note(keywords)
346 ui.note(keywords)
348 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
347 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
349 # silence branch command if not verbose
348 # silence branch command if not verbose
350 quiet = ui.quiet
349 quiet = ui.quiet
351 ui.quiet = not ui.verbose
350 ui.quiet = not ui.verbose
352 commands.branch(ui, repo, branchname)
351 commands.branch(ui, repo, branchname)
353 ui.quiet = quiet
352 ui.quiet = quiet
354 for name, cmd in ui.configitems('hooks'):
353 for name, cmd in ui.configitems('hooks'):
355 if name.split('.', 1)[0].find('commit') > -1:
354 if name.split('.', 1)[0].find('commit') > -1:
356 repo.ui.setconfig('hooks', name, '')
355 repo.ui.setconfig('hooks', name, '')
357 ui.note(_('unhooked all commit hooks\n'))
356 ui.note(_('unhooked all commit hooks\n'))
358 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
357 msg = _('hg keyword configuration and expansion example')
358 ui.note("hg -R '%s' ci -m '%s'\n" % (tmpdir, msg))
359 repo.commit(text=msg)
359 repo.commit(text=msg)
360 ui.status(_('\n\tkeywords expanded\n'))
360 ui.status(_('\n\tkeywords expanded\n'))
361 ui.write(repo.wread(fn))
361 ui.write(repo.wread(fn))
362 ui.debug('\nremoving temporary repository %s\n' % tmpdir)
362 ui.debug('\nremoving temporary repository %s\n' % tmpdir)
363 shutil.rmtree(tmpdir, ignore_errors=True)
363 shutil.rmtree(tmpdir, ignore_errors=True)
364
364
365 def expand(ui, repo, *pats, **opts):
365 def expand(ui, repo, *pats, **opts):
366 '''expand keywords in the working directory
366 '''expand keywords in the working directory
367
367
368 Run after (re)enabling keyword expansion.
368 Run after (re)enabling keyword expansion.
369
369
370 kwexpand refuses to run if given files contain local changes.
370 kwexpand refuses to run if given files contain local changes.
371 '''
371 '''
372 # 3rd argument sets expansion to True
372 # 3rd argument sets expansion to True
373 _kwfwrite(ui, repo, True, *pats, **opts)
373 _kwfwrite(ui, repo, True, *pats, **opts)
374
374
375 def files(ui, repo, *pats, **opts):
375 def files(ui, repo, *pats, **opts):
376 '''show files configured for keyword expansion
376 '''show files configured for keyword expansion
377
377
378 List which files in the working directory are matched by the
378 List which files in the working directory are matched by the
379 [keyword] configuration patterns.
379 [keyword] configuration patterns.
380
380
381 Useful to prevent inadvertent keyword expansion and to speed up
381 Useful to prevent inadvertent keyword expansion and to speed up
382 execution by including only files that are actual candidates for
382 execution by including only files that are actual candidates for
383 expansion.
383 expansion.
384
384
385 See "hg help keyword" on how to construct patterns both for
385 See "hg help keyword" on how to construct patterns both for
386 inclusion and exclusion of files.
386 inclusion and exclusion of files.
387
387
388 With -A/--all and -v/--verbose the codes used to show the status
388 With -A/--all and -v/--verbose the codes used to show the status
389 of files are::
389 of files are::
390
390
391 K = keyword expansion candidate
391 K = keyword expansion candidate
392 k = keyword expansion candidate (not tracked)
392 k = keyword expansion candidate (not tracked)
393 I = ignored
393 I = ignored
394 i = ignored (not tracked)
394 i = ignored (not tracked)
395 '''
395 '''
396 kwt = kwtools['templater']
396 kwt = kwtools['templater']
397 status = _status(ui, repo, kwt, *pats, **opts)
397 status = _status(ui, repo, kwt, *pats, **opts)
398 cwd = pats and repo.getcwd() or ''
398 cwd = pats and repo.getcwd() or ''
399 modified, added, removed, deleted, unknown, ignored, clean = status
399 modified, added, removed, deleted, unknown, ignored, clean = status
400 files = []
400 files = []
401 if not (opts.get('unknown') or opts.get('untracked')) or opts.get('all'):
401 if not (opts.get('unknown') or opts.get('untracked')) or opts.get('all'):
402 files = sorted(modified + added + clean)
402 files = sorted(modified + added + clean)
403 wctx = repo[None]
403 wctx = repo[None]
404 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
404 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
405 kwunknown = [f for f in unknown if kwt.iskwfile(f, wctx.flags)]
405 kwunknown = [f for f in unknown if kwt.iskwfile(f, wctx.flags)]
406 if not opts.get('ignore') or opts.get('all'):
406 if not opts.get('ignore') or opts.get('all'):
407 showfiles = kwfiles, kwunknown
407 showfiles = kwfiles, kwunknown
408 else:
408 else:
409 showfiles = [], []
409 showfiles = [], []
410 if opts.get('all') or opts.get('ignore'):
410 if opts.get('all') or opts.get('ignore'):
411 showfiles += ([f for f in files if f not in kwfiles],
411 showfiles += ([f for f in files if f not in kwfiles],
412 [f for f in unknown if f not in kwunknown])
412 [f for f in unknown if f not in kwunknown])
413 for char, filenames in zip('KkIi', showfiles):
413 for char, filenames in zip('KkIi', showfiles):
414 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
414 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
415 for f in filenames:
415 for f in filenames:
416 ui.write(fmt % repo.pathto(f, cwd))
416 ui.write(fmt % repo.pathto(f, cwd))
417
417
418 def shrink(ui, repo, *pats, **opts):
418 def shrink(ui, repo, *pats, **opts):
419 '''revert expanded keywords in the working directory
419 '''revert expanded keywords in the working directory
420
420
421 Run before changing/disabling active keywords or if you experience
421 Run before changing/disabling active keywords or if you experience
422 problems with "hg import" or "hg merge".
422 problems with "hg import" or "hg merge".
423
423
424 kwshrink refuses to run if given files contain local changes.
424 kwshrink refuses to run if given files contain local changes.
425 '''
425 '''
426 # 3rd argument sets expansion to False
426 # 3rd argument sets expansion to False
427 _kwfwrite(ui, repo, False, *pats, **opts)
427 _kwfwrite(ui, repo, False, *pats, **opts)
428
428
429
429
430 def uisetup(ui):
430 def uisetup(ui):
431 '''Collects [keyword] config in kwtools.
431 '''Collects [keyword] config in kwtools.
432 Monkeypatches dispatch._parse if needed.'''
432 Monkeypatches dispatch._parse if needed.'''
433
433
434 for pat, opt in ui.configitems('keyword'):
434 for pat, opt in ui.configitems('keyword'):
435 if opt != 'ignore':
435 if opt != 'ignore':
436 kwtools['inc'].append(pat)
436 kwtools['inc'].append(pat)
437 else:
437 else:
438 kwtools['exc'].append(pat)
438 kwtools['exc'].append(pat)
439
439
440 if kwtools['inc']:
440 if kwtools['inc']:
441 def kwdispatch_parse(orig, ui, args):
441 def kwdispatch_parse(orig, ui, args):
442 '''Monkeypatch dispatch._parse to obtain running hg command.'''
442 '''Monkeypatch dispatch._parse to obtain running hg command.'''
443 cmd, func, args, options, cmdoptions = orig(ui, args)
443 cmd, func, args, options, cmdoptions = orig(ui, args)
444 kwtools['hgcmd'] = cmd
444 kwtools['hgcmd'] = cmd
445 return cmd, func, args, options, cmdoptions
445 return cmd, func, args, options, cmdoptions
446
446
447 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
447 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
448
448
449 def reposetup(ui, repo):
449 def reposetup(ui, repo):
450 '''Sets up repo as kwrepo for keyword substitution.
450 '''Sets up repo as kwrepo for keyword substitution.
451 Overrides file method to return kwfilelog instead of filelog
451 Overrides file method to return kwfilelog instead of filelog
452 if file matches user configuration.
452 if file matches user configuration.
453 Wraps commit to overwrite configured files with updated
453 Wraps commit to overwrite configured files with updated
454 keyword substitutions.
454 keyword substitutions.
455 Monkeypatches patch and webcommands.'''
455 Monkeypatches patch and webcommands.'''
456
456
457 try:
457 try:
458 if (not repo.local() or not kwtools['inc']
458 if (not repo.local() or not kwtools['inc']
459 or kwtools['hgcmd'] in nokwcommands.split()
459 or kwtools['hgcmd'] in nokwcommands.split()
460 or '.hg' in util.splitpath(repo.root)
460 or '.hg' in util.splitpath(repo.root)
461 or repo._url.startswith('bundle:')):
461 or repo._url.startswith('bundle:')):
462 return
462 return
463 except AttributeError:
463 except AttributeError:
464 pass
464 pass
465
465
466 kwtools['templater'] = kwt = kwtemplater(ui, repo)
466 kwtools['templater'] = kwt = kwtemplater(ui, repo)
467
467
468 class kwrepo(repo.__class__):
468 class kwrepo(repo.__class__):
469 def file(self, f):
469 def file(self, f):
470 if f[0] == '/':
470 if f[0] == '/':
471 f = f[1:]
471 f = f[1:]
472 return kwfilelog(self.sopener, kwt, f)
472 return kwfilelog(self.sopener, kwt, f)
473
473
474 def wread(self, filename):
474 def wread(self, filename):
475 data = super(kwrepo, self).wread(filename)
475 data = super(kwrepo, self).wread(filename)
476 return kwt.wread(filename, data)
476 return kwt.wread(filename, data)
477
477
478 def commit(self, *args, **opts):
478 def commit(self, *args, **opts):
479 # use custom commitctx for user commands
479 # use custom commitctx for user commands
480 # other extensions can still wrap repo.commitctx directly
480 # other extensions can still wrap repo.commitctx directly
481 self.commitctx = self.kwcommitctx
481 self.commitctx = self.kwcommitctx
482 try:
482 try:
483 return super(kwrepo, self).commit(*args, **opts)
483 return super(kwrepo, self).commit(*args, **opts)
484 finally:
484 finally:
485 del self.commitctx
485 del self.commitctx
486
486
487 def kwcommitctx(self, ctx, error=False):
487 def kwcommitctx(self, ctx, error=False):
488 wlock = lock = None
488 wlock = lock = None
489 try:
489 try:
490 wlock = self.wlock()
490 wlock = self.wlock()
491 lock = self.lock()
491 lock = self.lock()
492 n = super(kwrepo, self).commitctx(ctx, error)
492 n = super(kwrepo, self).commitctx(ctx, error)
493 kwt.overwrite(n, True, None)
493 kwt.overwrite(n, True, None)
494 return n
494 return n
495 finally:
495 finally:
496 release(lock, wlock)
496 release(lock, wlock)
497
497
498 # monkeypatches
498 # monkeypatches
499 def kwpatchfile_init(orig, self, ui, fname, opener,
499 def kwpatchfile_init(orig, self, ui, fname, opener,
500 missing=False, eol=None):
500 missing=False, eol=None):
501 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
501 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
502 rejects or conflicts due to expanded keywords in working dir.'''
502 rejects or conflicts due to expanded keywords in working dir.'''
503 orig(self, ui, fname, opener, missing, eol)
503 orig(self, ui, fname, opener, missing, eol)
504 # shrink keywords read from working dir
504 # shrink keywords read from working dir
505 self.lines = kwt.shrinklines(self.fname, self.lines)
505 self.lines = kwt.shrinklines(self.fname, self.lines)
506
506
507 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
507 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
508 opts=None):
508 opts=None):
509 '''Monkeypatch patch.diff to avoid expansion except when
509 '''Monkeypatch patch.diff to avoid expansion except when
510 comparing against working dir.'''
510 comparing against working dir.'''
511 if node2 is not None:
511 if node2 is not None:
512 kwt.match = util.never
512 kwt.match = util.never
513 elif node1 is not None and node1 != repo['.'].node():
513 elif node1 is not None and node1 != repo['.'].node():
514 kwt.restrict = True
514 kwt.restrict = True
515 return orig(repo, node1, node2, match, changes, opts)
515 return orig(repo, node1, node2, match, changes, opts)
516
516
517 def kwweb_skip(orig, web, req, tmpl):
517 def kwweb_skip(orig, web, req, tmpl):
518 '''Wraps webcommands.x turning off keyword expansion.'''
518 '''Wraps webcommands.x turning off keyword expansion.'''
519 kwt.match = util.never
519 kwt.match = util.never
520 return orig(web, req, tmpl)
520 return orig(web, req, tmpl)
521
521
522 repo.__class__ = kwrepo
522 repo.__class__ = kwrepo
523
523
524 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
524 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
525 if not kwt.restrict:
525 if not kwt.restrict:
526 extensions.wrapfunction(patch, 'diff', kw_diff)
526 extensions.wrapfunction(patch, 'diff', kw_diff)
527 for c in 'annotate changeset rev filediff diff'.split():
527 for c in 'annotate changeset rev filediff diff'.split():
528 extensions.wrapfunction(webcommands, c, kwweb_skip)
528 extensions.wrapfunction(webcommands, c, kwweb_skip)
529
529
530 cmdtable = {
530 cmdtable = {
531 'kwdemo':
531 'kwdemo':
532 (demo,
532 (demo,
533 [('d', 'default', None, _('show default keyword template maps')),
533 [('d', 'default', None, _('show default keyword template maps')),
534 ('f', 'rcfile', '', _('read maps from rcfile'))],
534 ('f', 'rcfile', '', _('read maps from rcfile'))],
535 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
535 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
536 'kwexpand': (expand, commands.walkopts,
536 'kwexpand': (expand, commands.walkopts,
537 _('hg kwexpand [OPTION]... [FILE]...')),
537 _('hg kwexpand [OPTION]... [FILE]...')),
538 'kwfiles':
538 'kwfiles':
539 (files,
539 (files,
540 [('A', 'all', None, _('show keyword status flags of all files')),
540 [('A', 'all', None, _('show keyword status flags of all files')),
541 ('i', 'ignore', None, _('show files excluded from expansion')),
541 ('i', 'ignore', None, _('show files excluded from expansion')),
542 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
542 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
543 ('a', 'all', None,
543 ('a', 'all', None,
544 _('show keyword status flags of all files (DEPRECATED)')),
544 _('show keyword status flags of all files (DEPRECATED)')),
545 ('u', 'untracked', None, _('only show untracked files (DEPRECATED)')),
545 ('u', 'untracked', None, _('only show untracked files (DEPRECATED)')),
546 ] + commands.walkopts,
546 ] + commands.walkopts,
547 _('hg kwfiles [OPTION]... [FILE]...')),
547 _('hg kwfiles [OPTION]... [FILE]...')),
548 'kwshrink': (shrink, commands.walkopts,
548 'kwshrink': (shrink, commands.walkopts,
549 _('hg kwshrink [OPTION]... [FILE]...')),
549 _('hg kwshrink [OPTION]... [FILE]...')),
550 }
550 }
@@ -1,480 +1,480
1 % hg kwdemo
1 % hg kwdemo
2 [extensions]
2 [extensions]
3 keyword =
3 keyword =
4 [keyword]
4 [keyword]
5 demo.txt =
5 demo.txt =
6 [keywordmaps]
6 [keywordmaps]
7 Author = {author|user}
7 Author = {author|user}
8 Date = {date|utcdate}
8 Date = {date|utcdate}
9 Header = {root}/{file},v {node|short} {date|utcdate} {author|user}
9 Header = {root}/{file},v {node|short} {date|utcdate} {author|user}
10 Id = {file|basename},v {node|short} {date|utcdate} {author|user}
10 Id = {file|basename},v {node|short} {date|utcdate} {author|user}
11 RCSFile = {file|basename},v
11 RCSFile = {file|basename},v
12 RCSfile = {file|basename},v
12 RCSfile = {file|basename},v
13 Revision = {node|short}
13 Revision = {node|short}
14 Source = {root}/{file},v
14 Source = {root}/{file},v
15 $Author: test $
15 $Author: test $
16 $Date: 2000/00/00 00:00:00 $
16 $Date: 2000/00/00 00:00:00 $
17 $Header: /TMP/demo.txt,v xxxxxxxxxxxx 2000/00/00 00:00:00 test $
17 $Header: /TMP/demo.txt,v xxxxxxxxxxxx 2000/00/00 00:00:00 test $
18 $Id: demo.txt,v xxxxxxxxxxxx 2000/00/00 00:00:00 test $
18 $Id: demo.txt,v xxxxxxxxxxxx 2000/00/00 00:00:00 test $
19 $RCSFile: demo.txt,v $
19 $RCSFile: demo.txt,v $
20 $RCSfile: demo.txt,v $
20 $RCSfile: demo.txt,v $
21 $Revision: xxxxxxxxxxxx $
21 $Revision: xxxxxxxxxxxx $
22 $Source: /TMP/demo.txt,v $
22 $Source: /TMP/demo.txt,v $
23 [extensions]
23 [extensions]
24 keyword =
24 keyword =
25 [keyword]
25 [keyword]
26 demo.txt =
26 demo.txt =
27 [keywordmaps]
27 [keywordmaps]
28 Branch = {branches}
28 Branch = {branches}
29 $Branch: demobranch $
29 $Branch: demobranch $
30 % kwshrink should exit silently in empty/invalid repo
30 % kwshrink should exit silently in empty/invalid repo
31 pulling from test-keyword.hg
31 pulling from test-keyword.hg
32 requesting all changes
32 requesting all changes
33 adding changesets
33 adding changesets
34 adding manifests
34 adding manifests
35 adding file changes
35 adding file changes
36 added 1 changesets with 1 changes to 1 files
36 added 1 changesets with 1 changes to 1 files
37 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
37 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
38 % cat
38 % cat
39 expand $Id$
39 expand $Id$
40 do not process $Id:
40 do not process $Id:
41 xxx $
41 xxx $
42 ignore $Id$
42 ignore $Id$
43 % no kwfiles
43 % no kwfiles
44 % untracked candidates
44 % untracked candidates
45 k a
45 k a
46 % addremove
46 % addremove
47 adding a
47 adding a
48 adding b
48 adding b
49 % status
49 % status
50 A a
50 A a
51 A b
51 A b
52 % default keyword expansion including commit hook
52 % default keyword expansion including commit hook
53 % interrupted commit should not change state or run commit hook
53 % interrupted commit should not change state or run commit hook
54 abort: empty commit message
54 abort: empty commit message
55 % status
55 % status
56 A a
56 A a
57 A b
57 A b
58 % commit
58 % commit
59 a
59 a
60 b
60 b
61 overwriting a expanding keywords
61 overwriting a expanding keywords
62 running hook commit.test: cp a hooktest
62 running hook commit.test: cp a hooktest
63 committed changeset 1:ef63ca68695bc9495032c6fda1350c71e6d256e9
63 committed changeset 1:ef63ca68695bc9495032c6fda1350c71e6d256e9
64 % status
64 % status
65 ? hooktest
65 ? hooktest
66 % identify
66 % identify
67 ef63ca68695b
67 ef63ca68695b
68 % cat
68 % cat
69 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
69 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
70 do not process $Id:
70 do not process $Id:
71 xxx $
71 xxx $
72 ignore $Id$
72 ignore $Id$
73 % hg cat
73 % hg cat
74 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
74 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
75 do not process $Id:
75 do not process $Id:
76 xxx $
76 xxx $
77 ignore $Id$
77 ignore $Id$
78 a
78 a
79 % diff a hooktest
79 % diff a hooktest
80 % removing commit hook from config
80 % removing commit hook from config
81 % bundle
81 % bundle
82 2 changesets found
82 2 changesets found
83 % notify on pull to check whether keywords stay as is in email
83 % notify on pull to check whether keywords stay as is in email
84 % ie. if patch.diff wrapper acts as it should
84 % ie. if patch.diff wrapper acts as it should
85 % pull from bundle
85 % pull from bundle
86 pulling from ../kw.hg
86 pulling from ../kw.hg
87 requesting all changes
87 requesting all changes
88 adding changesets
88 adding changesets
89 adding manifests
89 adding manifests
90 adding file changes
90 adding file changes
91 added 2 changesets with 3 changes to 3 files
91 added 2 changesets with 3 changes to 3 files
92
92
93 diff -r 000000000000 -r a2392c293916 sym
93 diff -r 000000000000 -r a2392c293916 sym
94 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
94 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
95 +++ b/sym Sat Feb 09 20:25:47 2008 +0100
95 +++ b/sym Sat Feb 09 20:25:47 2008 +0100
96 @@ -0,0 +1,1 @@
96 @@ -0,0 +1,1 @@
97 +a
97 +a
98 \ No newline at end of file
98 \ No newline at end of file
99
99
100 diff -r a2392c293916 -r ef63ca68695b a
100 diff -r a2392c293916 -r ef63ca68695b a
101 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
101 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
102 +++ b/a Thu Jan 01 00:00:00 1970 +0000
102 +++ b/a Thu Jan 01 00:00:00 1970 +0000
103 @@ -0,0 +1,3 @@
103 @@ -0,0 +1,3 @@
104 +expand $Id$
104 +expand $Id$
105 +do not process $Id:
105 +do not process $Id:
106 +xxx $
106 +xxx $
107 diff -r a2392c293916 -r ef63ca68695b b
107 diff -r a2392c293916 -r ef63ca68695b b
108 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
108 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
109 +++ b/b Thu Jan 01 00:00:00 1970 +0000
109 +++ b/b Thu Jan 01 00:00:00 1970 +0000
110 @@ -0,0 +1,1 @@
110 @@ -0,0 +1,1 @@
111 +ignore $Id$
111 +ignore $Id$
112 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
112 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
113 % remove notify config
113 % remove notify config
114 % touch
114 % touch
115 % status
115 % status
116 % update
116 % update
117 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
118 % cat
118 % cat
119 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
119 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
120 do not process $Id:
120 do not process $Id:
121 xxx $
121 xxx $
122 ignore $Id$
122 ignore $Id$
123 % check whether expansion is filewise
123 % check whether expansion is filewise
124 % commit c
124 % commit c
125 adding c
125 adding c
126 % force expansion
126 % force expansion
127 overwriting a expanding keywords
127 overwriting a expanding keywords
128 overwriting c expanding keywords
128 overwriting c expanding keywords
129 % compare changenodes in a c
129 % compare changenodes in a c
130 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
130 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
131 do not process $Id:
131 do not process $Id:
132 xxx $
132 xxx $
133 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
133 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
134 tests for different changenodes
134 tests for different changenodes
135 % qinit -c
135 % qinit -c
136 % qimport
136 % qimport
137 % qcommit
137 % qcommit
138 % keywords should not be expanded in patch
138 % keywords should not be expanded in patch
139 # HG changeset patch
139 # HG changeset patch
140 # User User Name <user@example.com>
140 # User User Name <user@example.com>
141 # Date 1 0
141 # Date 1 0
142 # Node ID 40a904bbbe4cd4ab0a1f28411e35db26341a40ad
142 # Node ID 40a904bbbe4cd4ab0a1f28411e35db26341a40ad
143 # Parent ef63ca68695bc9495032c6fda1350c71e6d256e9
143 # Parent ef63ca68695bc9495032c6fda1350c71e6d256e9
144 cndiff
144 cndiff
145
145
146 diff -r ef63ca68695b -r 40a904bbbe4c c
146 diff -r ef63ca68695b -r 40a904bbbe4c c
147 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
147 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
148 +++ b/c Thu Jan 01 00:00:01 1970 +0000
148 +++ b/c Thu Jan 01 00:00:01 1970 +0000
149 @@ -0,0 +1,2 @@
149 @@ -0,0 +1,2 @@
150 +$Id$
150 +$Id$
151 +tests for different changenodes
151 +tests for different changenodes
152 % qpop
152 % qpop
153 popping mqtest.diff
153 popping mqtest.diff
154 patch queue now empty
154 patch queue now empty
155 % qgoto - should imply qpush
155 % qgoto - should imply qpush
156 applying mqtest.diff
156 applying mqtest.diff
157 now at: mqtest.diff
157 now at: mqtest.diff
158 % cat
158 % cat
159 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
159 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
160 tests for different changenodes
160 tests for different changenodes
161 % qpop and move on
161 % qpop and move on
162 popping mqtest.diff
162 popping mqtest.diff
163 patch queue now empty
163 patch queue now empty
164 % copy
164 % copy
165 % kwfiles added
165 % kwfiles added
166 a
166 a
167 c
167 c
168 % commit
168 % commit
169 c
169 c
170 c: copy a:0045e12f6c5791aac80ca6cbfd97709a88307292
170 c: copy a:0045e12f6c5791aac80ca6cbfd97709a88307292
171 overwriting c expanding keywords
171 overwriting c expanding keywords
172 committed changeset 2:25736cf2f5cbe41f6be4e6784ef6ecf9f3bbcc7d
172 committed changeset 2:25736cf2f5cbe41f6be4e6784ef6ecf9f3bbcc7d
173 % cat a c
173 % cat a c
174 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
174 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
175 do not process $Id:
175 do not process $Id:
176 xxx $
176 xxx $
177 expand $Id: c,v 25736cf2f5cb 1970/01/01 00:00:01 user $
177 expand $Id: c,v 25736cf2f5cb 1970/01/01 00:00:01 user $
178 do not process $Id:
178 do not process $Id:
179 xxx $
179 xxx $
180 % touch copied c
180 % touch copied c
181 % status
181 % status
182 % kwfiles
182 % kwfiles
183 a
183 a
184 c
184 c
185 % ignored files
185 % ignored files
186 I b
186 I b
187 I sym
187 I sym
188 % all files
188 % all files
189 K a
189 K a
190 K c
190 K c
191 I b
191 I b
192 I sym
192 I sym
193 % diff --rev
193 % diff --rev
194 diff -r ef63ca68695b c
194 diff -r ef63ca68695b c
195 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
195 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
196 @@ -0,0 +1,3 @@
196 @@ -0,0 +1,3 @@
197 +expand $Id$
197 +expand $Id$
198 +do not process $Id:
198 +do not process $Id:
199 +xxx $
199 +xxx $
200 % rollback
200 % rollback
201 rolling back last transaction
201 rolling back last transaction
202 % status
202 % status
203 A c
203 A c
204 % update -C
204 % update -C
205 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
205 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
206 % custom keyword expansion
206 % custom keyword expansion
207 % try with kwdemo
207 % try with kwdemo
208 [extensions]
208 [extensions]
209 keyword =
209 keyword =
210 [keyword]
210 [keyword]
211 * =
211 * =
212 b = ignore
212 b = ignore
213 demo.txt =
213 demo.txt =
214 [keywordmaps]
214 [keywordmaps]
215 Xinfo = {author}: {desc}
215 Xinfo = {author}: {desc}
216 $Xinfo: test: hg keyword config and expansion example $
216 $Xinfo: test: hg keyword configuration and expansion example $
217 % cat
217 % cat
218 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
218 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
219 do not process $Id:
219 do not process $Id:
220 xxx $
220 xxx $
221 ignore $Id$
221 ignore $Id$
222 % hg cat
222 % hg cat
223 expand $Id: a ef63ca68695b Thu, 01 Jan 1970 00:00:00 +0000 user $
223 expand $Id: a ef63ca68695b Thu, 01 Jan 1970 00:00:00 +0000 user $
224 do not process $Id:
224 do not process $Id:
225 xxx $
225 xxx $
226 ignore $Id$
226 ignore $Id$
227 a
227 a
228 % interrupted commit should not change state
228 % interrupted commit should not change state
229 abort: empty commit message
229 abort: empty commit message
230 % status
230 % status
231 M a
231 M a
232 ? c
232 ? c
233 ? log
233 ? log
234 % commit
234 % commit
235 a
235 a
236 overwriting a expanding keywords
236 overwriting a expanding keywords
237 committed changeset 2:bb948857c743469b22bbf51f7ec8112279ca5d83
237 committed changeset 2:bb948857c743469b22bbf51f7ec8112279ca5d83
238 % status
238 % status
239 ? c
239 ? c
240 % verify
240 % verify
241 checking changesets
241 checking changesets
242 checking manifests
242 checking manifests
243 crosschecking files in changesets and manifests
243 crosschecking files in changesets and manifests
244 checking files
244 checking files
245 3 files, 3 changesets, 4 total revisions
245 3 files, 3 changesets, 4 total revisions
246 % cat
246 % cat
247 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
247 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
248 do not process $Id:
248 do not process $Id:
249 xxx $
249 xxx $
250 $Xinfo: User Name <user@example.com>: firstline $
250 $Xinfo: User Name <user@example.com>: firstline $
251 ignore $Id$
251 ignore $Id$
252 % hg cat
252 % hg cat
253 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
253 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
254 do not process $Id:
254 do not process $Id:
255 xxx $
255 xxx $
256 $Xinfo: User Name <user@example.com>: firstline $
256 $Xinfo: User Name <user@example.com>: firstline $
257 ignore $Id$
257 ignore $Id$
258 a
258 a
259 % annotate
259 % annotate
260 1: expand $Id$
260 1: expand $Id$
261 1: do not process $Id:
261 1: do not process $Id:
262 1: xxx $
262 1: xxx $
263 2: $Xinfo$
263 2: $Xinfo$
264 % remove
264 % remove
265 committed changeset 3:d14c712653769de926994cf7fbb06c8fbd68f012
265 committed changeset 3:d14c712653769de926994cf7fbb06c8fbd68f012
266 % status
266 % status
267 ? c
267 ? c
268 % rollback
268 % rollback
269 rolling back last transaction
269 rolling back last transaction
270 % status
270 % status
271 R a
271 R a
272 ? c
272 ? c
273 % revert a
273 % revert a
274 % cat a
274 % cat a
275 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
275 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
276 do not process $Id:
276 do not process $Id:
277 xxx $
277 xxx $
278 $Xinfo: User Name <user@example.com>: firstline $
278 $Xinfo: User Name <user@example.com>: firstline $
279 % clone to test incoming
279 % clone to test incoming
280 requesting all changes
280 requesting all changes
281 adding changesets
281 adding changesets
282 adding manifests
282 adding manifests
283 adding file changes
283 adding file changes
284 added 2 changesets with 3 changes to 3 files
284 added 2 changesets with 3 changes to 3 files
285 updating to branch default
285 updating to branch default
286 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
286 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
287 % incoming
287 % incoming
288 comparing with test-keyword/Test
288 comparing with test-keyword/Test
289 searching for changes
289 searching for changes
290 changeset: 2:bb948857c743
290 changeset: 2:bb948857c743
291 tag: tip
291 tag: tip
292 user: User Name <user@example.com>
292 user: User Name <user@example.com>
293 date: Thu Jan 01 00:00:02 1970 +0000
293 date: Thu Jan 01 00:00:02 1970 +0000
294 summary: firstline
294 summary: firstline
295
295
296 % commit rejecttest
296 % commit rejecttest
297 a
297 a
298 overwriting a expanding keywords
298 overwriting a expanding keywords
299 committed changeset 2:85e279d709ffc28c9fdd1b868570985fc3d87082
299 committed changeset 2:85e279d709ffc28c9fdd1b868570985fc3d87082
300 % export
300 % export
301 % import
301 % import
302 applying ../rejecttest.diff
302 applying ../rejecttest.diff
303 % cat
303 % cat
304 expand $Id: a 4e0994474d25 Thu, 01 Jan 1970 00:00:03 +0000 user $ rejecttest
304 expand $Id: a 4e0994474d25 Thu, 01 Jan 1970 00:00:03 +0000 user $ rejecttest
305 do not process $Id: rejecttest
305 do not process $Id: rejecttest
306 xxx $
306 xxx $
307 $Xinfo: User Name <user@example.com>: rejects? $
307 $Xinfo: User Name <user@example.com>: rejects? $
308 ignore $Id$
308 ignore $Id$
309
309
310 % rollback
310 % rollback
311 rolling back last transaction
311 rolling back last transaction
312 % clean update
312 % clean update
313 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
313 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
314 % kwexpand/kwshrink on selected files
314 % kwexpand/kwshrink on selected files
315 % copy a x/a
315 % copy a x/a
316 % kwexpand a
316 % kwexpand a
317 overwriting a expanding keywords
317 overwriting a expanding keywords
318 % kwexpand x/a should abort
318 % kwexpand x/a should abort
319 abort: outstanding uncommitted changes
319 abort: outstanding uncommitted changes
320 x/a
320 x/a
321 x/a: copy a:779c764182ce5d43e2b1eb66ce06d7b47bfe342e
321 x/a: copy a:779c764182ce5d43e2b1eb66ce06d7b47bfe342e
322 overwriting x/a expanding keywords
322 overwriting x/a expanding keywords
323 committed changeset 3:b4560182a3f9a358179fd2d835c15e9da379c1e4
323 committed changeset 3:b4560182a3f9a358179fd2d835c15e9da379c1e4
324 % cat a
324 % cat a
325 expand $Id: x/a b4560182a3f9 Thu, 01 Jan 1970 00:00:03 +0000 user $
325 expand $Id: x/a b4560182a3f9 Thu, 01 Jan 1970 00:00:03 +0000 user $
326 do not process $Id:
326 do not process $Id:
327 xxx $
327 xxx $
328 $Xinfo: User Name <user@example.com>: xa $
328 $Xinfo: User Name <user@example.com>: xa $
329 % kwshrink a inside directory x
329 % kwshrink a inside directory x
330 overwriting x/a shrinking keywords
330 overwriting x/a shrinking keywords
331 % cat a
331 % cat a
332 expand $Id$
332 expand $Id$
333 do not process $Id:
333 do not process $Id:
334 xxx $
334 xxx $
335 $Xinfo$
335 $Xinfo$
336 % kwexpand nonexistent
336 % kwexpand nonexistent
337 nonexistent:
337 nonexistent:
338 % hg serve
338 % hg serve
339 % expansion
339 % expansion
340 % hgweb file
340 % hgweb file
341 200 Script output follows
341 200 Script output follows
342
342
343 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
343 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
344 do not process $Id:
344 do not process $Id:
345 xxx $
345 xxx $
346 $Xinfo: User Name <user@example.com>: firstline $
346 $Xinfo: User Name <user@example.com>: firstline $
347 % no expansion
347 % no expansion
348 % hgweb annotate
348 % hgweb annotate
349 200 Script output follows
349 200 Script output follows
350
350
351
351
352 user@1: expand $Id$
352 user@1: expand $Id$
353 user@1: do not process $Id:
353 user@1: do not process $Id:
354 user@1: xxx $
354 user@1: xxx $
355 user@2: $Xinfo$
355 user@2: $Xinfo$
356
356
357
357
358
358
359
359
360 % hgweb changeset
360 % hgweb changeset
361 200 Script output follows
361 200 Script output follows
362
362
363
363
364 # HG changeset patch
364 # HG changeset patch
365 # User User Name <user@example.com>
365 # User User Name <user@example.com>
366 # Date 3 0
366 # Date 3 0
367 # Node ID b4560182a3f9a358179fd2d835c15e9da379c1e4
367 # Node ID b4560182a3f9a358179fd2d835c15e9da379c1e4
368 # Parent bb948857c743469b22bbf51f7ec8112279ca5d83
368 # Parent bb948857c743469b22bbf51f7ec8112279ca5d83
369 xa
369 xa
370
370
371 diff -r bb948857c743 -r b4560182a3f9 x/a
371 diff -r bb948857c743 -r b4560182a3f9 x/a
372 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
372 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
373 +++ b/x/a Thu Jan 01 00:00:03 1970 +0000
373 +++ b/x/a Thu Jan 01 00:00:03 1970 +0000
374 @@ -0,0 +1,4 @@
374 @@ -0,0 +1,4 @@
375 +expand $Id$
375 +expand $Id$
376 +do not process $Id:
376 +do not process $Id:
377 +xxx $
377 +xxx $
378 +$Xinfo$
378 +$Xinfo$
379
379
380 % hgweb filediff
380 % hgweb filediff
381 200 Script output follows
381 200 Script output follows
382
382
383
383
384 diff -r ef63ca68695b -r bb948857c743 a
384 diff -r ef63ca68695b -r bb948857c743 a
385 --- a/a Thu Jan 01 00:00:00 1970 +0000
385 --- a/a Thu Jan 01 00:00:00 1970 +0000
386 +++ b/a Thu Jan 01 00:00:02 1970 +0000
386 +++ b/a Thu Jan 01 00:00:02 1970 +0000
387 @@ -1,3 +1,4 @@
387 @@ -1,3 +1,4 @@
388 expand $Id$
388 expand $Id$
389 do not process $Id:
389 do not process $Id:
390 xxx $
390 xxx $
391 +$Xinfo$
391 +$Xinfo$
392
392
393
393
394
394
395
395
396 % errors encountered
396 % errors encountered
397 % merge/resolve
397 % merge/resolve
398 % simplemerge
398 % simplemerge
399 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
399 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
400 created new head
400 created new head
401 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
401 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
402 (branch merge, don't forget to commit)
402 (branch merge, don't forget to commit)
403 $Id: m 27d48ee14f67 Thu, 01 Jan 1970 00:00:00 +0000 test $
403 $Id: m 27d48ee14f67 Thu, 01 Jan 1970 00:00:00 +0000 test $
404 foo
404 foo
405 % conflict
405 % conflict
406 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
406 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
407 created new head
407 created new head
408 merging m
408 merging m
409 warning: conflicts during merge.
409 warning: conflicts during merge.
410 merging m failed!
410 merging m failed!
411 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
411 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
412 use 'hg resolve' to retry unresolved file merges or 'hg update -C' to abandon
412 use 'hg resolve' to retry unresolved file merges or 'hg update -C' to abandon
413 % keyword stays outside conflict zone
413 % keyword stays outside conflict zone
414 $Id$
414 $Id$
415 <<<<<<< local
415 <<<<<<< local
416 bar
416 bar
417 =======
417 =======
418 foo
418 foo
419 >>>>>>> other
419 >>>>>>> other
420 % resolve to local
420 % resolve to local
421 $Id: m 41efa6d38e9b Thu, 01 Jan 1970 00:00:00 +0000 test $
421 $Id: m 41efa6d38e9b Thu, 01 Jan 1970 00:00:00 +0000 test $
422 bar
422 bar
423 % test restricted mode with transplant -b
423 % test restricted mode with transplant -b
424 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
424 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
425 marked working directory as branch foo
425 marked working directory as branch foo
426 created new head
426 created new head
427 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
427 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
428 applying 4aa30d025d50
428 applying 4aa30d025d50
429 4aa30d025d50 transplanted to 5a4da427c162
429 4aa30d025d50 transplanted to 5a4da427c162
430 % no expansion in changeset
430 % no expansion in changeset
431 changeset: 11:5a4da427c162
431 changeset: 11:5a4da427c162
432 tag: tip
432 tag: tip
433 parent: 9:41efa6d38e9b
433 parent: 9:41efa6d38e9b
434 user: test
434 user: test
435 date: Thu Jan 01 00:00:00 1970 +0000
435 date: Thu Jan 01 00:00:00 1970 +0000
436 summary: 9foobranch
436 summary: 9foobranch
437
437
438 diff -r 41efa6d38e9b -r 5a4da427c162 a
438 diff -r 41efa6d38e9b -r 5a4da427c162 a
439 --- a/a Thu Jan 01 00:00:00 1970 +0000
439 --- a/a Thu Jan 01 00:00:00 1970 +0000
440 +++ b/a Thu Jan 01 00:00:00 1970 +0000
440 +++ b/a Thu Jan 01 00:00:00 1970 +0000
441 @@ -1,3 +1,4 @@
441 @@ -1,3 +1,4 @@
442 +foobranch
442 +foobranch
443 expand $Id$
443 expand $Id$
444 do not process $Id:
444 do not process $Id:
445 xxx $
445 xxx $
446
446
447 % expansion in file
447 % expansion in file
448 foobranch
448 foobranch
449 expand $Id: a 5a4da427c162 Thu, 01 Jan 1970 00:00:00 +0000 test $
449 expand $Id: a 5a4da427c162 Thu, 01 Jan 1970 00:00:00 +0000 test $
450 % switch off expansion
450 % switch off expansion
451 % kwshrink with unknown file u
451 % kwshrink with unknown file u
452 overwriting a shrinking keywords
452 overwriting a shrinking keywords
453 overwriting m shrinking keywords
453 overwriting m shrinking keywords
454 overwriting x/a shrinking keywords
454 overwriting x/a shrinking keywords
455 % cat
455 % cat
456 expand $Id$
456 expand $Id$
457 do not process $Id:
457 do not process $Id:
458 xxx $
458 xxx $
459 $Xinfo$
459 $Xinfo$
460 ignore $Id$
460 ignore $Id$
461 % hg cat
461 % hg cat
462 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
462 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
463 do not process $Id:
463 do not process $Id:
464 xxx $
464 xxx $
465 $Xinfo: User Name <user@example.com>: firstline $
465 $Xinfo: User Name <user@example.com>: firstline $
466 ignore $Id$
466 ignore $Id$
467 a
467 a
468 % cat
468 % cat
469 expand $Id$
469 expand $Id$
470 do not process $Id:
470 do not process $Id:
471 xxx $
471 xxx $
472 $Xinfo$
472 $Xinfo$
473 ignore $Id$
473 ignore $Id$
474 % hg cat
474 % hg cat
475 expand $Id$
475 expand $Id$
476 do not process $Id:
476 do not process $Id:
477 xxx $
477 xxx $
478 $Xinfo$
478 $Xinfo$
479 ignore $Id$
479 ignore $Id$
480 a
480 a
General Comments 0
You need to be logged in to leave comments. Login now