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