##// END OF EJS Templates
Merge with stable
Martin Geisler -
r9944:15a1fe1f merge default
parent child Browse files
Show More
@@ -1,271 +1,279 b''
1 # extdiff.py - external diff program support for mercurial
1 # extdiff.py - external diff program support for mercurial
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
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, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 '''command to allow external programs to compare revisions
8 '''command to allow external programs to compare revisions
9
9
10 The extdiff Mercurial extension allows you to use external programs
10 The extdiff Mercurial extension allows you to use external programs
11 to compare revisions, or revision with working directory. The external
11 to compare revisions, or revision with working directory. The external
12 diff programs are called with a configurable set of options and two
12 diff programs are called with a configurable set of options and two
13 non-option arguments: paths to directories containing snapshots of
13 non-option arguments: paths to directories containing snapshots of
14 files to compare.
14 files to compare.
15
15
16 The extdiff extension also allows to configure new diff commands, so
16 The extdiff extension also allows to configure new diff commands, so
17 you do not need to type "hg extdiff -p kdiff3" always. ::
17 you do not need to type "hg extdiff -p kdiff3" always. ::
18
18
19 [extdiff]
19 [extdiff]
20 # add new command that runs GNU diff(1) in 'context diff' mode
20 # add new command that runs GNU diff(1) in 'context diff' mode
21 cdiff = gdiff -Nprc5
21 cdiff = gdiff -Nprc5
22 ## or the old way:
22 ## or the old way:
23 #cmd.cdiff = gdiff
23 #cmd.cdiff = gdiff
24 #opts.cdiff = -Nprc5
24 #opts.cdiff = -Nprc5
25
25
26 # add new command called vdiff, runs kdiff3
26 # add new command called vdiff, runs kdiff3
27 vdiff = kdiff3
27 vdiff = kdiff3
28
28
29 # add new command called meld, runs meld (no need to name twice)
29 # add new command called meld, runs meld (no need to name twice)
30 meld =
30 meld =
31
31
32 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
32 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
33 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
33 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
34 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
34 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
35 # your .vimrc
35 # your .vimrc
36 vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)'
36 vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)'
37
37
38 You can use -I/-X and list of file or directory names like normal "hg
38 You can use -I/-X and list of file or directory names like normal "hg
39 diff" command. The extdiff extension makes snapshots of only needed
39 diff" command. The extdiff extension makes snapshots of only needed
40 files, so running the external diff program will actually be pretty
40 files, so running the external diff program will actually be pretty
41 fast (at least faster than having to compare the entire tree).
41 fast (at least faster than having to compare the entire tree).
42 '''
42 '''
43
43
44 from mercurial.i18n import _
44 from mercurial.i18n import _
45 from mercurial.node import short, nullid
45 from mercurial.node import short, nullid
46 from mercurial import cmdutil, util, commands
46 from mercurial import cmdutil, util, commands, encoding
47 import os, shlex, shutil, tempfile, re
47 import os, shlex, shutil, tempfile, re
48
48
49 def snapshot(ui, repo, files, node, tmproot):
49 def snapshot(ui, repo, files, node, tmproot):
50 '''snapshot files as of some revision
50 '''snapshot files as of some revision
51 if not using snapshot, -I/-X does not work and recursive diff
51 if not using snapshot, -I/-X does not work and recursive diff
52 in tools like kdiff3 and meld displays too many files.'''
52 in tools like kdiff3 and meld displays too many files.'''
53 dirname = os.path.basename(repo.root)
53 dirname = os.path.basename(repo.root)
54 if dirname == "":
54 if dirname == "":
55 dirname = "root"
55 dirname = "root"
56 if node is not None:
56 if node is not None:
57 dirname = '%s.%s' % (dirname, short(node))
57 dirname = '%s.%s' % (dirname, short(node))
58 base = os.path.join(tmproot, dirname)
58 base = os.path.join(tmproot, dirname)
59 os.mkdir(base)
59 os.mkdir(base)
60 if node is not None:
60 if node is not None:
61 ui.note(_('making snapshot of %d files from rev %s\n') %
61 ui.note(_('making snapshot of %d files from rev %s\n') %
62 (len(files), short(node)))
62 (len(files), short(node)))
63 else:
63 else:
64 ui.note(_('making snapshot of %d files from working directory\n') %
64 ui.note(_('making snapshot of %d files from working directory\n') %
65 (len(files)))
65 (len(files)))
66 wopener = util.opener(base)
66 wopener = util.opener(base)
67 fns_and_mtime = []
67 fns_and_mtime = []
68 ctx = repo[node]
68 ctx = repo[node]
69 for fn in files:
69 for fn in files:
70 wfn = util.pconvert(fn)
70 wfn = util.pconvert(fn)
71 if not wfn in ctx:
71 if not wfn in ctx:
72 # File doesn't exist; could be a bogus modify
72 # File doesn't exist; could be a bogus modify
73 continue
73 continue
74 ui.note(' %s\n' % wfn)
74 ui.note(' %s\n' % wfn)
75 dest = os.path.join(base, wfn)
75 dest = os.path.join(base, wfn)
76 fctx = ctx[wfn]
76 fctx = ctx[wfn]
77 data = repo.wwritedata(wfn, fctx.data())
77 data = repo.wwritedata(wfn, fctx.data())
78 if 'l' in fctx.flags():
78 if 'l' in fctx.flags():
79 wopener.symlink(data, wfn)
79 wopener.symlink(data, wfn)
80 else:
80 else:
81 wopener(wfn, 'w').write(data)
81 wopener(wfn, 'w').write(data)
82 if 'x' in fctx.flags():
82 if 'x' in fctx.flags():
83 util.set_flags(dest, False, True)
83 util.set_flags(dest, False, True)
84 if node is None:
84 if node is None:
85 fns_and_mtime.append((dest, repo.wjoin(fn), os.path.getmtime(dest)))
85 fns_and_mtime.append((dest, repo.wjoin(fn), os.path.getmtime(dest)))
86 return dirname, fns_and_mtime
86 return dirname, fns_and_mtime
87
87
88 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
88 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
89 '''Do the actuall diff:
89 '''Do the actuall diff:
90
90
91 - copy to a temp structure if diffing 2 internal revisions
91 - copy to a temp structure if diffing 2 internal revisions
92 - copy to a temp structure if diffing working revision with
92 - copy to a temp structure if diffing working revision with
93 another one and more than 1 file is changed
93 another one and more than 1 file is changed
94 - just invoke the diff for a single file in the working dir
94 - just invoke the diff for a single file in the working dir
95 '''
95 '''
96
96
97 revs = opts.get('rev')
97 revs = opts.get('rev')
98 change = opts.get('change')
98 change = opts.get('change')
99 args = ' '.join(diffopts)
99 args = ' '.join(diffopts)
100 do3way = '$parent2' in args
100 do3way = '$parent2' in args
101
101
102 if revs and change:
102 if revs and change:
103 msg = _('cannot specify --rev and --change at the same time')
103 msg = _('cannot specify --rev and --change at the same time')
104 raise util.Abort(msg)
104 raise util.Abort(msg)
105 elif change:
105 elif change:
106 node2 = repo.lookup(change)
106 node2 = repo.lookup(change)
107 node1a, node1b = repo.changelog.parents(node2)
107 node1a, node1b = repo.changelog.parents(node2)
108 else:
108 else:
109 node1a, node2 = cmdutil.revpair(repo, revs)
109 node1a, node2 = cmdutil.revpair(repo, revs)
110 if not revs:
110 if not revs:
111 node1b = repo.dirstate.parents()[1]
111 node1b = repo.dirstate.parents()[1]
112 else:
112 else:
113 node1b = nullid
113 node1b = nullid
114
114
115 # Disable 3-way merge if there is only one parent
115 # Disable 3-way merge if there is only one parent
116 if do3way:
116 if do3way:
117 if node1b == nullid:
117 if node1b == nullid:
118 do3way = False
118 do3way = False
119
119
120 matcher = cmdutil.match(repo, pats, opts)
120 matcher = cmdutil.match(repo, pats, opts)
121 mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3])
121 mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3])
122 if do3way:
122 if do3way:
123 mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher)[:3])
123 mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher)[:3])
124 else:
124 else:
125 mod_b, add_b, rem_b = set(), set(), set()
125 mod_b, add_b, rem_b = set(), set(), set()
126 modadd = mod_a | add_a | mod_b | add_b
126 modadd = mod_a | add_a | mod_b | add_b
127 common = modadd | rem_a | rem_b
127 common = modadd | rem_a | rem_b
128 if not common:
128 if not common:
129 return 0
129 return 0
130
130
131 tmproot = tempfile.mkdtemp(prefix='extdiff.')
131 tmproot = tempfile.mkdtemp(prefix='extdiff.')
132 try:
132 try:
133 # Always make a copy of node1a (and node1b, if applicable)
133 # Always make a copy of node1a (and node1b, if applicable)
134 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
134 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
135 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0]
135 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0]
136 if do3way:
136 if do3way:
137 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
137 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
138 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0]
138 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0]
139 else:
139 else:
140 dir1b = None
140 dir1b = None
141
141
142 fns_and_mtime = []
142 fns_and_mtime = []
143
143
144 # If node2 in not the wc or there is >1 change, copy it
144 # If node2 in not the wc or there is >1 change, copy it
145 dir2root = ''
145 dir2root = ''
146 if node2:
146 if node2:
147 dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0]
147 dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0]
148 elif len(common) > 1:
148 elif len(common) > 1:
149 #we only actually need to get the files to copy back to the working
149 #we only actually need to get the files to copy back to the working
150 #dir in this case (because the other cases are: diffing 2 revisions
150 #dir in this case (because the other cases are: diffing 2 revisions
151 #or single file -- in which case the file is already directly passed
151 #or single file -- in which case the file is already directly passed
152 #to the diff tool).
152 #to the diff tool).
153 dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot)
153 dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot)
154 else:
154 else:
155 # This lets the diff tool open the changed file directly
155 # This lets the diff tool open the changed file directly
156 dir2 = ''
156 dir2 = ''
157 dir2root = repo.root
157 dir2root = repo.root
158
158
159 # If only one change, diff the files instead of the directories
159 # If only one change, diff the files instead of the directories
160 # Handle bogus modifies correctly by checking if the files exist
160 # Handle bogus modifies correctly by checking if the files exist
161 if len(common) == 1:
161 if len(common) == 1:
162 common_file = util.localpath(common.pop())
162 common_file = util.localpath(common.pop())
163 dir1a = os.path.join(dir1a, common_file)
163 dir1a = os.path.join(dir1a, common_file)
164 if not os.path.isfile(os.path.join(tmproot, dir1a)):
164 if not os.path.isfile(os.path.join(tmproot, dir1a)):
165 dir1a = os.devnull
165 dir1a = os.devnull
166 if do3way:
166 if do3way:
167 dir1b = os.path.join(dir1b, common_file)
167 dir1b = os.path.join(dir1b, common_file)
168 if not os.path.isfile(os.path.join(tmproot, dir1b)):
168 if not os.path.isfile(os.path.join(tmproot, dir1b)):
169 dir1b = os.devnull
169 dir1b = os.devnull
170 dir2 = os.path.join(dir2root, dir2, common_file)
170 dir2 = os.path.join(dir2root, dir2, common_file)
171
171
172 # Function to quote file/dir names in the argument string
172 # Function to quote file/dir names in the argument string
173 # When not operating in 3-way mode, an empty string is returned for parent2
173 # When not operating in 3-way mode, an empty string is returned for parent2
174 replace = dict(parent=dir1a, parent1=dir1a, parent2=dir1b, child=dir2)
174 replace = dict(parent=dir1a, parent1=dir1a, parent2=dir1b, child=dir2)
175 def quote(match):
175 def quote(match):
176 key = match.group()[1:]
176 key = match.group()[1:]
177 if not do3way and key == 'parent2':
177 if not do3way and key == 'parent2':
178 return ''
178 return ''
179 return util.shellquote(replace[key])
179 return util.shellquote(replace[key])
180
180
181 # Match parent2 first, so 'parent1?' will match both parent1 and parent
181 # Match parent2 first, so 'parent1?' will match both parent1 and parent
182 regex = '\$(parent2|parent1?|child)'
182 regex = '\$(parent2|parent1?|child)'
183 if not do3way and not re.search(regex, args):
183 if not do3way and not re.search(regex, args):
184 args += ' $parent1 $child'
184 args += ' $parent1 $child'
185 args = re.sub(regex, quote, args)
185 args = re.sub(regex, quote, args)
186 cmdline = util.shellquote(diffcmd) + ' ' + args
186 cmdline = util.shellquote(diffcmd) + ' ' + args
187
187
188 ui.debug('running %r in %s\n' % (cmdline, tmproot))
188 ui.debug('running %r in %s\n' % (cmdline, tmproot))
189 util.system(cmdline, cwd=tmproot)
189 util.system(cmdline, cwd=tmproot)
190
190
191 for copy_fn, working_fn, mtime in fns_and_mtime:
191 for copy_fn, working_fn, mtime in fns_and_mtime:
192 if os.path.getmtime(copy_fn) != mtime:
192 if os.path.getmtime(copy_fn) != mtime:
193 ui.debug('file changed while diffing. '
193 ui.debug('file changed while diffing. '
194 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
194 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
195 util.copyfile(copy_fn, working_fn)
195 util.copyfile(copy_fn, working_fn)
196
196
197 return 1
197 return 1
198 finally:
198 finally:
199 ui.note(_('cleaning up temp directory\n'))
199 ui.note(_('cleaning up temp directory\n'))
200 shutil.rmtree(tmproot)
200 shutil.rmtree(tmproot)
201
201
202 def extdiff(ui, repo, *pats, **opts):
202 def extdiff(ui, repo, *pats, **opts):
203 '''use external program to diff repository (or selected files)
203 '''use external program to diff repository (or selected files)
204
204
205 Show differences between revisions for the specified files, using
205 Show differences between revisions for the specified files, using
206 an external program. The default program used is diff, with
206 an external program. The default program used is diff, with
207 default options "-Npru".
207 default options "-Npru".
208
208
209 To select a different program, use the -p/--program option. The
209 To select a different program, use the -p/--program option. The
210 program will be passed the names of two directories to compare. To
210 program will be passed the names of two directories to compare. To
211 pass additional options to the program, use -o/--option. These
211 pass additional options to the program, use -o/--option. These
212 will be passed before the names of the directories to compare.
212 will be passed before the names of the directories to compare.
213
213
214 When two revision arguments are given, then changes are shown
214 When two revision arguments are given, then changes are shown
215 between those revisions. If only one revision is specified then
215 between those revisions. If only one revision is specified then
216 that revision is compared to the working directory, and, when no
216 that revision is compared to the working directory, and, when no
217 revisions are specified, the working directory files are compared
217 revisions are specified, the working directory files are compared
218 to its parent.'''
218 to its parent.'''
219 program = opts.get('program')
219 program = opts.get('program')
220 option = opts.get('option')
220 option = opts.get('option')
221 if not program:
221 if not program:
222 program = 'diff'
222 program = 'diff'
223 option = option or ['-Npru']
223 option = option or ['-Npru']
224 return dodiff(ui, repo, program, option, pats, opts)
224 return dodiff(ui, repo, program, option, pats, opts)
225
225
226 cmdtable = {
226 cmdtable = {
227 "extdiff":
227 "extdiff":
228 (extdiff,
228 (extdiff,
229 [('p', 'program', '', _('comparison program to run')),
229 [('p', 'program', '', _('comparison program to run')),
230 ('o', 'option', [], _('pass option to comparison program')),
230 ('o', 'option', [], _('pass option to comparison program')),
231 ('r', 'rev', [], _('revision')),
231 ('r', 'rev', [], _('revision')),
232 ('c', 'change', '', _('change made by revision')),
232 ('c', 'change', '', _('change made by revision')),
233 ] + commands.walkopts,
233 ] + commands.walkopts,
234 _('hg extdiff [OPT]... [FILE]...')),
234 _('hg extdiff [OPT]... [FILE]...')),
235 }
235 }
236
236
237 def uisetup(ui):
237 def uisetup(ui):
238 for cmd, path in ui.configitems('extdiff'):
238 for cmd, path in ui.configitems('extdiff'):
239 if cmd.startswith('cmd.'):
239 if cmd.startswith('cmd.'):
240 cmd = cmd[4:]
240 cmd = cmd[4:]
241 if not path: path = cmd
241 if not path: path = cmd
242 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
242 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
243 diffopts = diffopts and [diffopts] or []
243 diffopts = diffopts and [diffopts] or []
244 elif cmd.startswith('opts.'):
244 elif cmd.startswith('opts.'):
245 continue
245 continue
246 else:
246 else:
247 # command = path opts
247 # command = path opts
248 if path:
248 if path:
249 diffopts = shlex.split(path)
249 diffopts = shlex.split(path)
250 path = diffopts.pop(0)
250 path = diffopts.pop(0)
251 else:
251 else:
252 path, diffopts = cmd, []
252 path, diffopts = cmd, []
253 def save(cmd, path, diffopts):
253 def save(cmd, path, diffopts):
254 '''use closure to save diff command to use'''
254 '''use closure to save diff command to use'''
255 def mydiff(ui, repo, *pats, **opts):
255 def mydiff(ui, repo, *pats, **opts):
256 return dodiff(ui, repo, path, diffopts, pats, opts)
256 return dodiff(ui, repo, path, diffopts, pats, opts)
257 mydiff.__doc__ = _('''\
257 doc = _('''\
258 use %(path)s to diff repository (or selected files)
258 use %(path)s to diff repository (or selected files)
259
259
260 Show differences between revisions for the specified files, using the
260 Show differences between revisions for the specified files, using the
261 %(path)s program.
261 %(path)s program.
262
262
263 When two revision arguments are given, then changes are shown between
263 When two revision arguments are given, then changes are shown between
264 those revisions. If only one revision is specified then that revision is
264 those revisions. If only one revision is specified then that revision is
265 compared to the working directory, and, when no revisions are specified,
265 compared to the working directory, and, when no revisions are specified,
266 the working directory files are compared to its parent.\
266 the working directory files are compared to its parent.\
267 ''') % dict(path=util.uirepr(path))
267 ''') % dict(path=util.uirepr(path))
268
269 # We must translate the docstring right away since it is
270 # used as a format string. The string will unfortunately
271 # be translated again in commands.helpcmd and this will
272 # fail when the docstring contains non-ASCII characters.
273 # Decoding the string to a Unicode string here (using the
274 # right encoding) prevents that.
275 mydiff.__doc__ = doc.decode(encoding.encoding)
268 return mydiff
276 return mydiff
269 cmdtable[cmd] = (save(cmd, path, diffopts),
277 cmdtable[cmd] = (save(cmd, path, diffopts),
270 cmdtable['extdiff'][1][1:],
278 cmdtable['extdiff'][1][1:],
271 _('hg %s [OPTION]... [FILE]...') % cmd)
279 _('hg %s [OPTION]... [FILE]...') % cmd)
@@ -1,562 +1,563 b''
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, incorporated herein by reference.
6 # GNU General Public License version 2, 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
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
97
98 # provide cvs-like UTC date filter
98 # provide cvs-like UTC date filter
99 utcdate = lambda x: util.datestr(x, '%Y/%m/%d %H:%M:%S')
99 utcdate = lambda x: util.datestr(x, '%Y/%m/%d %H:%M:%S')
100
100
101 # make keyword tools accessible
101 # make keyword tools accessible
102 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
102 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
103
103
104
104
105 class kwtemplater(object):
105 class kwtemplater(object):
106 '''
106 '''
107 Sets up keyword templates, corresponding keyword regex, and
107 Sets up keyword templates, corresponding keyword regex, and
108 provides keyword substitution functions.
108 provides keyword substitution functions.
109 '''
109 '''
110 templates = {
110 templates = {
111 'Revision': '{node|short}',
111 'Revision': '{node|short}',
112 'Author': '{author|user}',
112 'Author': '{author|user}',
113 'Date': '{date|utcdate}',
113 'Date': '{date|utcdate}',
114 'RCSFile': '{file|basename},v',
114 'RCSfile': '{file|basename},v',
115 'RCSFile': '{file|basename},v', # kept only for backwards compatibility
115 'Source': '{root}/{file},v',
116 'Source': '{root}/{file},v',
116 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
117 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
117 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
118 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
118 }
119 }
119
120
120 def __init__(self, ui, repo):
121 def __init__(self, ui, repo):
121 self.ui = ui
122 self.ui = ui
122 self.repo = repo
123 self.repo = repo
123 self.match = match.match(repo.root, '', [],
124 self.match = match.match(repo.root, '', [],
124 kwtools['inc'], kwtools['exc'])
125 kwtools['inc'], kwtools['exc'])
125 self.restrict = kwtools['hgcmd'] in restricted.split()
126 self.restrict = kwtools['hgcmd'] in restricted.split()
126
127
127 kwmaps = self.ui.configitems('keywordmaps')
128 kwmaps = self.ui.configitems('keywordmaps')
128 if kwmaps: # override default templates
129 if kwmaps: # override default templates
129 self.templates = dict((k, templater.parsestring(v, False))
130 self.templates = dict((k, templater.parsestring(v, False))
130 for k, v in kwmaps)
131 for k, v in kwmaps)
131 escaped = map(re.escape, self.templates.keys())
132 escaped = map(re.escape, self.templates.keys())
132 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
133 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
133 self.re_kw = re.compile(kwpat)
134 self.re_kw = re.compile(kwpat)
134
135
135 templatefilters.filters['utcdate'] = utcdate
136 templatefilters.filters['utcdate'] = utcdate
136 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
137 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
137 False, None, '', False)
138 False, None, '', False)
138
139
139 def substitute(self, data, path, ctx, subfunc):
140 def substitute(self, data, path, ctx, subfunc):
140 '''Replaces keywords in data with expanded template.'''
141 '''Replaces keywords in data with expanded template.'''
141 def kwsub(mobj):
142 def kwsub(mobj):
142 kw = mobj.group(1)
143 kw = mobj.group(1)
143 self.ct.use_template(self.templates[kw])
144 self.ct.use_template(self.templates[kw])
144 self.ui.pushbuffer()
145 self.ui.pushbuffer()
145 self.ct.show(ctx, root=self.repo.root, file=path)
146 self.ct.show(ctx, root=self.repo.root, file=path)
146 ekw = templatefilters.firstline(self.ui.popbuffer())
147 ekw = templatefilters.firstline(self.ui.popbuffer())
147 return '$%s: %s $' % (kw, ekw)
148 return '$%s: %s $' % (kw, ekw)
148 return subfunc(kwsub, data)
149 return subfunc(kwsub, data)
149
150
150 def expand(self, path, node, data):
151 def expand(self, path, node, data):
151 '''Returns data with keywords expanded.'''
152 '''Returns data with keywords expanded.'''
152 if not self.restrict and self.match(path) and not util.binary(data):
153 if not self.restrict and self.match(path) and not util.binary(data):
153 ctx = self.repo.filectx(path, fileid=node).changectx()
154 ctx = self.repo.filectx(path, fileid=node).changectx()
154 return self.substitute(data, path, ctx, self.re_kw.sub)
155 return self.substitute(data, path, ctx, self.re_kw.sub)
155 return data
156 return data
156
157
157 def iskwfile(self, path, flagfunc):
158 def iskwfile(self, path, flagfunc):
158 '''Returns true if path matches [keyword] pattern
159 '''Returns true if path matches [keyword] pattern
159 and is not a symbolic link.
160 and is not a symbolic link.
160 Caveat: localrepository._link fails on Windows.'''
161 Caveat: localrepository._link fails on Windows.'''
161 return self.match(path) and not 'l' in flagfunc(path)
162 return self.match(path) and not 'l' in flagfunc(path)
162
163
163 def overwrite(self, node, expand, files):
164 def overwrite(self, node, expand, files):
164 '''Overwrites selected files expanding/shrinking keywords.'''
165 '''Overwrites selected files expanding/shrinking keywords.'''
165 ctx = self.repo[node]
166 ctx = self.repo[node]
166 mf = ctx.manifest()
167 mf = ctx.manifest()
167 if node is not None: # commit
168 if node is not None: # commit
168 files = [f for f in ctx.files() if f in mf]
169 files = [f for f in ctx.files() if f in mf]
169 notify = self.ui.debug
170 notify = self.ui.debug
170 else: # kwexpand/kwshrink
171 else: # kwexpand/kwshrink
171 notify = self.ui.note
172 notify = self.ui.note
172 candidates = [f for f in files if self.iskwfile(f, ctx.flags)]
173 candidates = [f for f in files if self.iskwfile(f, ctx.flags)]
173 if candidates:
174 if candidates:
174 self.restrict = True # do not expand when reading
175 self.restrict = True # do not expand when reading
175 msg = (expand and _('overwriting %s expanding keywords\n')
176 msg = (expand and _('overwriting %s expanding keywords\n')
176 or _('overwriting %s shrinking keywords\n'))
177 or _('overwriting %s shrinking keywords\n'))
177 for f in candidates:
178 for f in candidates:
178 fp = self.repo.file(f)
179 fp = self.repo.file(f)
179 data = fp.read(mf[f])
180 data = fp.read(mf[f])
180 if util.binary(data):
181 if util.binary(data):
181 continue
182 continue
182 if expand:
183 if expand:
183 if node is None:
184 if node is None:
184 ctx = self.repo.filectx(f, fileid=mf[f]).changectx()
185 ctx = self.repo.filectx(f, fileid=mf[f]).changectx()
185 data, found = self.substitute(data, f, ctx,
186 data, found = self.substitute(data, f, ctx,
186 self.re_kw.subn)
187 self.re_kw.subn)
187 else:
188 else:
188 found = self.re_kw.search(data)
189 found = self.re_kw.search(data)
189 if found:
190 if found:
190 notify(msg % f)
191 notify(msg % f)
191 self.repo.wwrite(f, data, mf.flags(f))
192 self.repo.wwrite(f, data, mf.flags(f))
192 if node is None:
193 if node is None:
193 self.repo.dirstate.normal(f)
194 self.repo.dirstate.normal(f)
194 self.restrict = False
195 self.restrict = False
195
196
196 def shrinktext(self, text):
197 def shrinktext(self, text):
197 '''Unconditionally removes all keyword substitutions from text.'''
198 '''Unconditionally removes all keyword substitutions from text.'''
198 return self.re_kw.sub(r'$\1$', text)
199 return self.re_kw.sub(r'$\1$', text)
199
200
200 def shrink(self, fname, text):
201 def shrink(self, fname, text):
201 '''Returns text with all keyword substitutions removed.'''
202 '''Returns text with all keyword substitutions removed.'''
202 if self.match(fname) and not util.binary(text):
203 if self.match(fname) and not util.binary(text):
203 return self.shrinktext(text)
204 return self.shrinktext(text)
204 return text
205 return text
205
206
206 def shrinklines(self, fname, lines):
207 def shrinklines(self, fname, lines):
207 '''Returns lines with keyword substitutions removed.'''
208 '''Returns lines with keyword substitutions removed.'''
208 if self.match(fname):
209 if self.match(fname):
209 text = ''.join(lines)
210 text = ''.join(lines)
210 if not util.binary(text):
211 if not util.binary(text):
211 return self.shrinktext(text).splitlines(True)
212 return self.shrinktext(text).splitlines(True)
212 return lines
213 return lines
213
214
214 def wread(self, fname, data):
215 def wread(self, fname, data):
215 '''If in restricted mode returns data read from wdir with
216 '''If in restricted mode returns data read from wdir with
216 keyword substitutions removed.'''
217 keyword substitutions removed.'''
217 return self.restrict and self.shrink(fname, data) or data
218 return self.restrict and self.shrink(fname, data) or data
218
219
219 class kwfilelog(filelog.filelog):
220 class kwfilelog(filelog.filelog):
220 '''
221 '''
221 Subclass of filelog to hook into its read, add, cmp methods.
222 Subclass of filelog to hook into its read, add, cmp methods.
222 Keywords are "stored" unexpanded, and processed on reading.
223 Keywords are "stored" unexpanded, and processed on reading.
223 '''
224 '''
224 def __init__(self, opener, kwt, path):
225 def __init__(self, opener, kwt, path):
225 super(kwfilelog, self).__init__(opener, path)
226 super(kwfilelog, self).__init__(opener, path)
226 self.kwt = kwt
227 self.kwt = kwt
227 self.path = path
228 self.path = path
228
229
229 def read(self, node):
230 def read(self, node):
230 '''Expands keywords when reading filelog.'''
231 '''Expands keywords when reading filelog.'''
231 data = super(kwfilelog, self).read(node)
232 data = super(kwfilelog, self).read(node)
232 return self.kwt.expand(self.path, node, data)
233 return self.kwt.expand(self.path, node, data)
233
234
234 def add(self, text, meta, tr, link, p1=None, p2=None):
235 def add(self, text, meta, tr, link, p1=None, p2=None):
235 '''Removes keyword substitutions when adding to filelog.'''
236 '''Removes keyword substitutions when adding to filelog.'''
236 text = self.kwt.shrink(self.path, text)
237 text = self.kwt.shrink(self.path, text)
237 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
238 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
238
239
239 def cmp(self, node, text):
240 def cmp(self, node, text):
240 '''Removes keyword substitutions for comparison.'''
241 '''Removes keyword substitutions for comparison.'''
241 text = self.kwt.shrink(self.path, text)
242 text = self.kwt.shrink(self.path, text)
242 if self.renamed(node):
243 if self.renamed(node):
243 t2 = super(kwfilelog, self).read(node)
244 t2 = super(kwfilelog, self).read(node)
244 return t2 != text
245 return t2 != text
245 return revlog.revlog.cmp(self, node, text)
246 return revlog.revlog.cmp(self, node, text)
246
247
247 def _status(ui, repo, kwt, *pats, **opts):
248 def _status(ui, repo, kwt, *pats, **opts):
248 '''Bails out if [keyword] configuration is not active.
249 '''Bails out if [keyword] configuration is not active.
249 Returns status of working directory.'''
250 Returns status of working directory.'''
250 if kwt:
251 if kwt:
251 unknown = (opts.get('unknown') or opts.get('all')
252 unknown = (opts.get('unknown') or opts.get('all')
252 or opts.get('untracked'))
253 or opts.get('untracked'))
253 return repo.status(match=cmdutil.match(repo, pats, opts), clean=True,
254 return repo.status(match=cmdutil.match(repo, pats, opts), clean=True,
254 unknown=unknown)
255 unknown=unknown)
255 if ui.configitems('keyword'):
256 if ui.configitems('keyword'):
256 raise util.Abort(_('[keyword] patterns cannot match'))
257 raise util.Abort(_('[keyword] patterns cannot match'))
257 raise util.Abort(_('no [keyword] patterns configured'))
258 raise util.Abort(_('no [keyword] patterns configured'))
258
259
259 def _kwfwrite(ui, repo, expand, *pats, **opts):
260 def _kwfwrite(ui, repo, expand, *pats, **opts):
260 '''Selects files and passes them to kwtemplater.overwrite.'''
261 '''Selects files and passes them to kwtemplater.overwrite.'''
261 if repo.dirstate.parents()[1] != nullid:
262 if repo.dirstate.parents()[1] != nullid:
262 raise util.Abort(_('outstanding uncommitted merge'))
263 raise util.Abort(_('outstanding uncommitted merge'))
263 kwt = kwtools['templater']
264 kwt = kwtools['templater']
264 status = _status(ui, repo, kwt, *pats, **opts)
265 status = _status(ui, repo, kwt, *pats, **opts)
265 modified, added, removed, deleted = status[:4]
266 modified, added, removed, deleted = status[:4]
266 if modified or added or removed or deleted:
267 if modified or added or removed or deleted:
267 raise util.Abort(_('outstanding uncommitted changes'))
268 raise util.Abort(_('outstanding uncommitted changes'))
268 wlock = lock = None
269 wlock = lock = None
269 try:
270 try:
270 wlock = repo.wlock()
271 wlock = repo.wlock()
271 lock = repo.lock()
272 lock = repo.lock()
272 kwt.overwrite(None, expand, status[6])
273 kwt.overwrite(None, expand, status[6])
273 finally:
274 finally:
274 release(lock, wlock)
275 release(lock, wlock)
275
276
276 def demo(ui, repo, *args, **opts):
277 def demo(ui, repo, *args, **opts):
277 '''print [keywordmaps] configuration and an expansion example
278 '''print [keywordmaps] configuration and an expansion example
278
279
279 Show current, custom, or default keyword template maps and their
280 Show current, custom, or default keyword template maps and their
280 expansions.
281 expansions.
281
282
282 Extend the current configuration by specifying maps as arguments
283 Extend the current configuration by specifying maps as arguments
283 and using -f/--rcfile to source an external hgrc file.
284 and using -f/--rcfile to source an external hgrc file.
284
285
285 Use -d/--default to disable current configuration.
286 Use -d/--default to disable current configuration.
286
287
287 See "hg help templates" for information on templates and filters.
288 See "hg help templates" for information on templates and filters.
288 '''
289 '''
289 def demoitems(section, items):
290 def demoitems(section, items):
290 ui.write('[%s]\n' % section)
291 ui.write('[%s]\n' % section)
291 for k, v in items:
292 for k, v in sorted(items):
292 ui.write('%s = %s\n' % (k, v))
293 ui.write('%s = %s\n' % (k, v))
293
294
294 msg = 'hg keyword config and expansion example'
295 msg = 'hg keyword config and expansion example'
295 fn = 'demo.txt'
296 fn = 'demo.txt'
296 branchname = 'demobranch'
297 branchname = 'demobranch'
297 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
298 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
298 ui.note(_('creating temporary repository at %s\n') % tmpdir)
299 ui.note(_('creating temporary repository at %s\n') % tmpdir)
299 repo = localrepo.localrepository(ui, tmpdir, True)
300 repo = localrepo.localrepository(ui, tmpdir, True)
300 ui.setconfig('keyword', fn, '')
301 ui.setconfig('keyword', fn, '')
301
302
302 uikwmaps = ui.configitems('keywordmaps')
303 uikwmaps = ui.configitems('keywordmaps')
303 if args or opts.get('rcfile'):
304 if args or opts.get('rcfile'):
304 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
305 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
305 if uikwmaps:
306 if uikwmaps:
306 ui.status(_('\textending current template maps\n'))
307 ui.status(_('\textending current template maps\n'))
307 if opts.get('default') or not uikwmaps:
308 if opts.get('default') or not uikwmaps:
308 ui.status(_('\toverriding default template maps\n'))
309 ui.status(_('\toverriding default template maps\n'))
309 if opts.get('rcfile'):
310 if opts.get('rcfile'):
310 ui.readconfig(opts.get('rcfile'))
311 ui.readconfig(opts.get('rcfile'))
311 if args:
312 if args:
312 # simulate hgrc parsing
313 # simulate hgrc parsing
313 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
314 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
314 fp = repo.opener('hgrc', 'w')
315 fp = repo.opener('hgrc', 'w')
315 fp.writelines(rcmaps)
316 fp.writelines(rcmaps)
316 fp.close()
317 fp.close()
317 ui.readconfig(repo.join('hgrc'))
318 ui.readconfig(repo.join('hgrc'))
318 kwmaps = dict(ui.configitems('keywordmaps'))
319 kwmaps = dict(ui.configitems('keywordmaps'))
319 elif opts.get('default'):
320 elif opts.get('default'):
320 ui.status(_('\n\tconfiguration using default keyword template maps\n'))
321 ui.status(_('\n\tconfiguration using default keyword template maps\n'))
321 kwmaps = kwtemplater.templates
322 kwmaps = kwtemplater.templates
322 if uikwmaps:
323 if uikwmaps:
323 ui.status(_('\tdisabling current template maps\n'))
324 ui.status(_('\tdisabling current template maps\n'))
324 for k, v in kwmaps.iteritems():
325 for k, v in kwmaps.iteritems():
325 ui.setconfig('keywordmaps', k, v)
326 ui.setconfig('keywordmaps', k, v)
326 else:
327 else:
327 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
328 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
328 kwmaps = dict(uikwmaps) or kwtemplater.templates
329 kwmaps = dict(uikwmaps) or kwtemplater.templates
329
330
330 uisetup(ui)
331 uisetup(ui)
331 reposetup(ui, repo)
332 reposetup(ui, repo)
332 for k, v in ui.configitems('extensions'):
333 for k, v in ui.configitems('extensions'):
333 if k.endswith('keyword'):
334 if k.endswith('keyword'):
334 extension = '%s = %s' % (k, v)
335 extension = '%s = %s' % (k, v)
335 break
336 break
336 ui.write('[extensions]\n%s\n' % extension)
337 ui.write('[extensions]\n%s\n' % extension)
337 demoitems('keyword', ui.configitems('keyword'))
338 demoitems('keyword', ui.configitems('keyword'))
338 demoitems('keywordmaps', kwmaps.iteritems())
339 demoitems('keywordmaps', kwmaps.iteritems())
339 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
340 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
340 repo.wopener(fn, 'w').write(keywords)
341 repo.wopener(fn, 'w').write(keywords)
341 repo.add([fn])
342 repo.add([fn])
342 path = repo.wjoin(fn)
343 path = repo.wjoin(fn)
343 ui.note(_('\nkeywords written to %s:\n') % path)
344 ui.note(_('\nkeywords written to %s:\n') % path)
344 ui.note(keywords)
345 ui.note(keywords)
345 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
346 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
346 # silence branch command if not verbose
347 # silence branch command if not verbose
347 quiet = ui.quiet
348 quiet = ui.quiet
348 ui.quiet = not ui.verbose
349 ui.quiet = not ui.verbose
349 commands.branch(ui, repo, branchname)
350 commands.branch(ui, repo, branchname)
350 ui.quiet = quiet
351 ui.quiet = quiet
351 for name, cmd in ui.configitems('hooks'):
352 for name, cmd in ui.configitems('hooks'):
352 if name.split('.', 1)[0].find('commit') > -1:
353 if name.split('.', 1)[0].find('commit') > -1:
353 repo.ui.setconfig('hooks', name, '')
354 repo.ui.setconfig('hooks', name, '')
354 ui.note(_('unhooked all commit hooks\n'))
355 ui.note(_('unhooked all commit hooks\n'))
355 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
356 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
356 repo.commit(text=msg)
357 repo.commit(text=msg)
357 ui.status(_('\n\tkeywords expanded\n'))
358 ui.status(_('\n\tkeywords expanded\n'))
358 ui.write(repo.wread(fn))
359 ui.write(repo.wread(fn))
359 ui.debug('\nremoving temporary repository %s\n' % tmpdir)
360 ui.debug('\nremoving temporary repository %s\n' % tmpdir)
360 shutil.rmtree(tmpdir, ignore_errors=True)
361 shutil.rmtree(tmpdir, ignore_errors=True)
361
362
362 def expand(ui, repo, *pats, **opts):
363 def expand(ui, repo, *pats, **opts):
363 '''expand keywords in the working directory
364 '''expand keywords in the working directory
364
365
365 Run after (re)enabling keyword expansion.
366 Run after (re)enabling keyword expansion.
366
367
367 kwexpand refuses to run if given files contain local changes.
368 kwexpand refuses to run if given files contain local changes.
368 '''
369 '''
369 # 3rd argument sets expansion to True
370 # 3rd argument sets expansion to True
370 _kwfwrite(ui, repo, True, *pats, **opts)
371 _kwfwrite(ui, repo, True, *pats, **opts)
371
372
372 def files(ui, repo, *pats, **opts):
373 def files(ui, repo, *pats, **opts):
373 '''show files configured for keyword expansion
374 '''show files configured for keyword expansion
374
375
375 List which files in the working directory are matched by the
376 List which files in the working directory are matched by the
376 [keyword] configuration patterns.
377 [keyword] configuration patterns.
377
378
378 Useful to prevent inadvertent keyword expansion and to speed up
379 Useful to prevent inadvertent keyword expansion and to speed up
379 execution by including only files that are actual candidates for
380 execution by including only files that are actual candidates for
380 expansion.
381 expansion.
381
382
382 See "hg help keyword" on how to construct patterns both for
383 See "hg help keyword" on how to construct patterns both for
383 inclusion and exclusion of files.
384 inclusion and exclusion of files.
384
385
385 With -A/--all and -v/--verbose the codes used to show the status
386 With -A/--all and -v/--verbose the codes used to show the status
386 of files are::
387 of files are::
387
388
388 K = keyword expansion candidate
389 K = keyword expansion candidate
389 k = keyword expansion candidate (not tracked)
390 k = keyword expansion candidate (not tracked)
390 I = ignored
391 I = ignored
391 i = ignored (not tracked)
392 i = ignored (not tracked)
392 '''
393 '''
393 kwt = kwtools['templater']
394 kwt = kwtools['templater']
394 status = _status(ui, repo, kwt, *pats, **opts)
395 status = _status(ui, repo, kwt, *pats, **opts)
395 cwd = pats and repo.getcwd() or ''
396 cwd = pats and repo.getcwd() or ''
396 modified, added, removed, deleted, unknown, ignored, clean = status
397 modified, added, removed, deleted, unknown, ignored, clean = status
397 files = []
398 files = []
398 if not (opts.get('unknown') or opts.get('untracked')) or opts.get('all'):
399 if not (opts.get('unknown') or opts.get('untracked')) or opts.get('all'):
399 files = sorted(modified + added + clean)
400 files = sorted(modified + added + clean)
400 wctx = repo[None]
401 wctx = repo[None]
401 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
402 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
402 kwunknown = [f for f in unknown if kwt.iskwfile(f, wctx.flags)]
403 kwunknown = [f for f in unknown if kwt.iskwfile(f, wctx.flags)]
403 if not opts.get('ignore') or opts.get('all'):
404 if not opts.get('ignore') or opts.get('all'):
404 showfiles = kwfiles, kwunknown
405 showfiles = kwfiles, kwunknown
405 else:
406 else:
406 showfiles = [], []
407 showfiles = [], []
407 if opts.get('all') or opts.get('ignore'):
408 if opts.get('all') or opts.get('ignore'):
408 showfiles += ([f for f in files if f not in kwfiles],
409 showfiles += ([f for f in files if f not in kwfiles],
409 [f for f in unknown if f not in kwunknown])
410 [f for f in unknown if f not in kwunknown])
410 for char, filenames in zip('KkIi', showfiles):
411 for char, filenames in zip('KkIi', showfiles):
411 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
412 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
412 for f in filenames:
413 for f in filenames:
413 ui.write(fmt % repo.pathto(f, cwd))
414 ui.write(fmt % repo.pathto(f, cwd))
414
415
415 def shrink(ui, repo, *pats, **opts):
416 def shrink(ui, repo, *pats, **opts):
416 '''revert expanded keywords in the working directory
417 '''revert expanded keywords in the working directory
417
418
418 Run before changing/disabling active keywords or if you experience
419 Run before changing/disabling active keywords or if you experience
419 problems with "hg import" or "hg merge".
420 problems with "hg import" or "hg merge".
420
421
421 kwshrink refuses to run if given files contain local changes.
422 kwshrink refuses to run if given files contain local changes.
422 '''
423 '''
423 # 3rd argument sets expansion to False
424 # 3rd argument sets expansion to False
424 _kwfwrite(ui, repo, False, *pats, **opts)
425 _kwfwrite(ui, repo, False, *pats, **opts)
425
426
426
427
427 def uisetup(ui):
428 def uisetup(ui):
428 '''Collects [keyword] config in kwtools.
429 '''Collects [keyword] config in kwtools.
429 Monkeypatches dispatch._parse if needed.'''
430 Monkeypatches dispatch._parse if needed.'''
430
431
431 for pat, opt in ui.configitems('keyword'):
432 for pat, opt in ui.configitems('keyword'):
432 if opt != 'ignore':
433 if opt != 'ignore':
433 kwtools['inc'].append(pat)
434 kwtools['inc'].append(pat)
434 else:
435 else:
435 kwtools['exc'].append(pat)
436 kwtools['exc'].append(pat)
436
437
437 if kwtools['inc']:
438 if kwtools['inc']:
438 def kwdispatch_parse(orig, ui, args):
439 def kwdispatch_parse(orig, ui, args):
439 '''Monkeypatch dispatch._parse to obtain running hg command.'''
440 '''Monkeypatch dispatch._parse to obtain running hg command.'''
440 cmd, func, args, options, cmdoptions = orig(ui, args)
441 cmd, func, args, options, cmdoptions = orig(ui, args)
441 kwtools['hgcmd'] = cmd
442 kwtools['hgcmd'] = cmd
442 return cmd, func, args, options, cmdoptions
443 return cmd, func, args, options, cmdoptions
443
444
444 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
445 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
445
446
446 def reposetup(ui, repo):
447 def reposetup(ui, repo):
447 '''Sets up repo as kwrepo for keyword substitution.
448 '''Sets up repo as kwrepo for keyword substitution.
448 Overrides file method to return kwfilelog instead of filelog
449 Overrides file method to return kwfilelog instead of filelog
449 if file matches user configuration.
450 if file matches user configuration.
450 Wraps commit to overwrite configured files with updated
451 Wraps commit to overwrite configured files with updated
451 keyword substitutions.
452 keyword substitutions.
452 Monkeypatches patch and webcommands.'''
453 Monkeypatches patch and webcommands.'''
453
454
454 try:
455 try:
455 if (not repo.local() or not kwtools['inc']
456 if (not repo.local() or not kwtools['inc']
456 or kwtools['hgcmd'] in nokwcommands.split()
457 or kwtools['hgcmd'] in nokwcommands.split()
457 or '.hg' in util.splitpath(repo.root)
458 or '.hg' in util.splitpath(repo.root)
458 or repo._url.startswith('bundle:')):
459 or repo._url.startswith('bundle:')):
459 return
460 return
460 except AttributeError:
461 except AttributeError:
461 pass
462 pass
462
463
463 kwtools['templater'] = kwt = kwtemplater(ui, repo)
464 kwtools['templater'] = kwt = kwtemplater(ui, repo)
464
465
465 class kwrepo(repo.__class__):
466 class kwrepo(repo.__class__):
466 def file(self, f):
467 def file(self, f):
467 if f[0] == '/':
468 if f[0] == '/':
468 f = f[1:]
469 f = f[1:]
469 return kwfilelog(self.sopener, kwt, f)
470 return kwfilelog(self.sopener, kwt, f)
470
471
471 def wread(self, filename):
472 def wread(self, filename):
472 data = super(kwrepo, self).wread(filename)
473 data = super(kwrepo, self).wread(filename)
473 return kwt.wread(filename, data)
474 return kwt.wread(filename, data)
474
475
475 def commit(self, *args, **opts):
476 def commit(self, *args, **opts):
476 # use custom commitctx for user commands
477 # use custom commitctx for user commands
477 # other extensions can still wrap repo.commitctx directly
478 # other extensions can still wrap repo.commitctx directly
478 self.commitctx = self.kwcommitctx
479 self.commitctx = self.kwcommitctx
479 try:
480 try:
480 return super(kwrepo, self).commit(*args, **opts)
481 return super(kwrepo, self).commit(*args, **opts)
481 finally:
482 finally:
482 del self.commitctx
483 del self.commitctx
483
484
484 def kwcommitctx(self, ctx, error=False):
485 def kwcommitctx(self, ctx, error=False):
485 wlock = lock = None
486 wlock = lock = None
486 try:
487 try:
487 wlock = self.wlock()
488 wlock = self.wlock()
488 lock = self.lock()
489 lock = self.lock()
489 # store and postpone commit hooks
490 # store and postpone commit hooks
490 commithooks = {}
491 commithooks = {}
491 for name, cmd in ui.configitems('hooks'):
492 for name, cmd in ui.configitems('hooks'):
492 if name.split('.', 1)[0] == 'commit':
493 if name.split('.', 1)[0] == 'commit':
493 commithooks[name] = cmd
494 commithooks[name] = cmd
494 ui.setconfig('hooks', name, None)
495 ui.setconfig('hooks', name, None)
495 if commithooks:
496 if commithooks:
496 # store parents for commit hooks
497 # store parents for commit hooks
497 p1, p2 = ctx.p1(), ctx.p2()
498 p1, p2 = ctx.p1(), ctx.p2()
498 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
499 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
499
500
500 n = super(kwrepo, self).commitctx(ctx, error)
501 n = super(kwrepo, self).commitctx(ctx, error)
501
502
502 kwt.overwrite(n, True, None)
503 kwt.overwrite(n, True, None)
503 if commithooks:
504 if commithooks:
504 for name, cmd in commithooks.iteritems():
505 for name, cmd in commithooks.iteritems():
505 ui.setconfig('hooks', name, cmd)
506 ui.setconfig('hooks', name, cmd)
506 self.hook('commit', node=n, parent1=xp1, parent2=xp2)
507 self.hook('commit', node=n, parent1=xp1, parent2=xp2)
507 return n
508 return n
508 finally:
509 finally:
509 release(lock, wlock)
510 release(lock, wlock)
510
511
511 # monkeypatches
512 # monkeypatches
512 def kwpatchfile_init(orig, self, ui, fname, opener,
513 def kwpatchfile_init(orig, self, ui, fname, opener,
513 missing=False, eol=None):
514 missing=False, eol=None):
514 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
515 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
515 rejects or conflicts due to expanded keywords in working dir.'''
516 rejects or conflicts due to expanded keywords in working dir.'''
516 orig(self, ui, fname, opener, missing, eol)
517 orig(self, ui, fname, opener, missing, eol)
517 # shrink keywords read from working dir
518 # shrink keywords read from working dir
518 self.lines = kwt.shrinklines(self.fname, self.lines)
519 self.lines = kwt.shrinklines(self.fname, self.lines)
519
520
520 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
521 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
521 opts=None):
522 opts=None):
522 '''Monkeypatch patch.diff to avoid expansion except when
523 '''Monkeypatch patch.diff to avoid expansion except when
523 comparing against working dir.'''
524 comparing against working dir.'''
524 if node2 is not None:
525 if node2 is not None:
525 kwt.match = util.never
526 kwt.match = util.never
526 elif node1 is not None and node1 != repo['.'].node():
527 elif node1 is not None and node1 != repo['.'].node():
527 kwt.restrict = True
528 kwt.restrict = True
528 return orig(repo, node1, node2, match, changes, opts)
529 return orig(repo, node1, node2, match, changes, opts)
529
530
530 def kwweb_skip(orig, web, req, tmpl):
531 def kwweb_skip(orig, web, req, tmpl):
531 '''Wraps webcommands.x turning off keyword expansion.'''
532 '''Wraps webcommands.x turning off keyword expansion.'''
532 kwt.match = util.never
533 kwt.match = util.never
533 return orig(web, req, tmpl)
534 return orig(web, req, tmpl)
534
535
535 repo.__class__ = kwrepo
536 repo.__class__ = kwrepo
536
537
537 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
538 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
538 extensions.wrapfunction(patch, 'diff', kw_diff)
539 extensions.wrapfunction(patch, 'diff', kw_diff)
539 for c in 'annotate changeset rev filediff diff'.split():
540 for c in 'annotate changeset rev filediff diff'.split():
540 extensions.wrapfunction(webcommands, c, kwweb_skip)
541 extensions.wrapfunction(webcommands, c, kwweb_skip)
541
542
542 cmdtable = {
543 cmdtable = {
543 'kwdemo':
544 'kwdemo':
544 (demo,
545 (demo,
545 [('d', 'default', None, _('show default keyword template maps')),
546 [('d', 'default', None, _('show default keyword template maps')),
546 ('f', 'rcfile', '', _('read maps from rcfile'))],
547 ('f', 'rcfile', '', _('read maps from rcfile'))],
547 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
548 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
548 'kwexpand': (expand, commands.walkopts,
549 'kwexpand': (expand, commands.walkopts,
549 _('hg kwexpand [OPTION]... [FILE]...')),
550 _('hg kwexpand [OPTION]... [FILE]...')),
550 'kwfiles':
551 'kwfiles':
551 (files,
552 (files,
552 [('A', 'all', None, _('show keyword status flags of all files')),
553 [('A', 'all', None, _('show keyword status flags of all files')),
553 ('i', 'ignore', None, _('show files excluded from expansion')),
554 ('i', 'ignore', None, _('show files excluded from expansion')),
554 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
555 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
555 ('a', 'all', None,
556 ('a', 'all', None,
556 _('show keyword status flags of all files (DEPRECATED)')),
557 _('show keyword status flags of all files (DEPRECATED)')),
557 ('u', 'untracked', None, _('only show untracked files (DEPRECATED)')),
558 ('u', 'untracked', None, _('only show untracked files (DEPRECATED)')),
558 ] + commands.walkopts,
559 ] + commands.walkopts,
559 _('hg kwfiles [OPTION]... [FILE]...')),
560 _('hg kwfiles [OPTION]... [FILE]...')),
560 'kwshrink': (shrink, commands.walkopts,
561 'kwshrink': (shrink, commands.walkopts,
561 _('hg kwshrink [OPTION]... [FILE]...')),
562 _('hg kwshrink [OPTION]... [FILE]...')),
562 }
563 }
@@ -1,451 +1,453 b''
1 % hg kwdemo
1 % hg kwdemo
2 [extensions]
2 [extensions]
3 hgext.keyword =
3 hgext.keyword =
4 [keyword]
4 [keyword]
5 demo.txt =
5 demo.txt =
6 [keywordmaps]
6 [keywordmaps]
7 RCSFile = {file|basename},v
8 Author = {author|user}
7 Author = {author|user}
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 Source = {root}/{file},v
11 Date = {date|utcdate}
12 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
12 RCSfile = {file|basename},v
13 Revision = {node|short}
13 Revision = {node|short}
14 $RCSFile: demo.txt,v $
14 Source = {root}/{file},v
15 $Author: test $
15 $Author: test $
16 $Date: 2000/00/00 00:00:00 $
16 $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 $
19 $RCSFile: demo.txt,v $
20 $RCSfile: demo.txt,v $
21 $Revision: xxxxxxxxxxxx $
17 $Source: /TMP/demo.txt,v $
22 $Source: /TMP/demo.txt,v $
18 $Date: 2000/00/00 00:00:00 $
19 $Id: demo.txt,v xxxxxxxxxxxx 2000/00/00 00:00:00 test $
20 $Revision: xxxxxxxxxxxx $
21 [extensions]
23 [extensions]
22 hgext.keyword =
24 hgext.keyword =
23 [keyword]
25 [keyword]
24 demo.txt =
26 demo.txt =
25 [keywordmaps]
27 [keywordmaps]
26 Branch = {branches}
28 Branch = {branches}
27 $Branch: demobranch $
29 $Branch: demobranch $
28 % kwshrink should exit silently in empty/invalid repo
30 % kwshrink should exit silently in empty/invalid repo
29 pulling from test-keyword.hg
31 pulling from test-keyword.hg
30 requesting all changes
32 requesting all changes
31 adding changesets
33 adding changesets
32 adding manifests
34 adding manifests
33 adding file changes
35 adding file changes
34 added 1 changesets with 1 changes to 1 files
36 added 1 changesets with 1 changes to 1 files
35 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
36 % cat
38 % cat
37 expand $Id$
39 expand $Id$
38 do not process $Id:
40 do not process $Id:
39 xxx $
41 xxx $
40 ignore $Id$
42 ignore $Id$
41 % no kwfiles
43 % no kwfiles
42 % untracked candidates
44 % untracked candidates
43 k a
45 k a
44 % addremove
46 % addremove
45 adding a
47 adding a
46 adding b
48 adding b
47 % status
49 % status
48 A a
50 A a
49 A b
51 A b
50 % default keyword expansion including commit hook
52 % default keyword expansion including commit hook
51 % interrupted commit should not change state or run commit hook
53 % interrupted commit should not change state or run commit hook
52 abort: empty commit message
54 abort: empty commit message
53 % status
55 % status
54 A a
56 A a
55 A b
57 A b
56 % commit
58 % commit
57 a
59 a
58 b
60 b
59 overwriting a expanding keywords
61 overwriting a expanding keywords
60 running hook commit.test: cp a hooktest
62 running hook commit.test: cp a hooktest
61 committed changeset 1:ef63ca68695bc9495032c6fda1350c71e6d256e9
63 committed changeset 1:ef63ca68695bc9495032c6fda1350c71e6d256e9
62 % status
64 % status
63 ? hooktest
65 ? hooktest
64 % identify
66 % identify
65 ef63ca68695b
67 ef63ca68695b
66 % cat
68 % cat
67 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 $
68 do not process $Id:
70 do not process $Id:
69 xxx $
71 xxx $
70 ignore $Id$
72 ignore $Id$
71 % hg cat
73 % hg cat
72 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 $
73 do not process $Id:
75 do not process $Id:
74 xxx $
76 xxx $
75 ignore $Id$
77 ignore $Id$
76 a
78 a
77 % diff a hooktest
79 % diff a hooktest
78 % removing commit hook from config
80 % removing commit hook from config
79 % bundle
81 % bundle
80 2 changesets found
82 2 changesets found
81 % 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
82 % ie. if patch.diff wrapper acts as it should
84 % ie. if patch.diff wrapper acts as it should
83 % pull from bundle
85 % pull from bundle
84 pulling from ../kw.hg
86 pulling from ../kw.hg
85 requesting all changes
87 requesting all changes
86 adding changesets
88 adding changesets
87 adding manifests
89 adding manifests
88 adding file changes
90 adding file changes
89 added 2 changesets with 3 changes to 3 files
91 added 2 changesets with 3 changes to 3 files
90
92
91 diff -r 000000000000 -r a2392c293916 sym
93 diff -r 000000000000 -r a2392c293916 sym
92 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
94 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
93 +++ b/sym Sat Feb 09 20:25:47 2008 +0100
95 +++ b/sym Sat Feb 09 20:25:47 2008 +0100
94 @@ -0,0 +1,1 @@
96 @@ -0,0 +1,1 @@
95 +a
97 +a
96 \ No newline at end of file
98 \ No newline at end of file
97
99
98 diff -r a2392c293916 -r ef63ca68695b a
100 diff -r a2392c293916 -r ef63ca68695b a
99 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
101 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
100 +++ b/a Thu Jan 01 00:00:00 1970 +0000
102 +++ b/a Thu Jan 01 00:00:00 1970 +0000
101 @@ -0,0 +1,3 @@
103 @@ -0,0 +1,3 @@
102 +expand $Id$
104 +expand $Id$
103 +do not process $Id:
105 +do not process $Id:
104 +xxx $
106 +xxx $
105 diff -r a2392c293916 -r ef63ca68695b b
107 diff -r a2392c293916 -r ef63ca68695b b
106 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
108 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
107 +++ b/b Thu Jan 01 00:00:00 1970 +0000
109 +++ b/b Thu Jan 01 00:00:00 1970 +0000
108 @@ -0,0 +1,1 @@
110 @@ -0,0 +1,1 @@
109 +ignore $Id$
111 +ignore $Id$
110 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
111 % remove notify config
113 % remove notify config
112 % touch
114 % touch
113 % status
115 % status
114 % update
116 % update
115 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
116 % cat
118 % cat
117 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 $
118 do not process $Id:
120 do not process $Id:
119 xxx $
121 xxx $
120 ignore $Id$
122 ignore $Id$
121 % check whether expansion is filewise
123 % check whether expansion is filewise
122 % commit c
124 % commit c
123 adding c
125 adding c
124 % force expansion
126 % force expansion
125 overwriting a expanding keywords
127 overwriting a expanding keywords
126 overwriting c expanding keywords
128 overwriting c expanding keywords
127 % compare changenodes in a c
129 % compare changenodes in a c
128 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 $
129 do not process $Id:
131 do not process $Id:
130 xxx $
132 xxx $
131 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
133 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
132 tests for different changenodes
134 tests for different changenodes
133 % qinit -c
135 % qinit -c
134 % qimport
136 % qimport
135 % qcommit
137 % qcommit
136 % keywords should not be expanded in patch
138 % keywords should not be expanded in patch
137 # HG changeset patch
139 # HG changeset patch
138 # User User Name <user@example.com>
140 # User User Name <user@example.com>
139 # Date 1 0
141 # Date 1 0
140 # Node ID 40a904bbbe4cd4ab0a1f28411e35db26341a40ad
142 # Node ID 40a904bbbe4cd4ab0a1f28411e35db26341a40ad
141 # Parent ef63ca68695bc9495032c6fda1350c71e6d256e9
143 # Parent ef63ca68695bc9495032c6fda1350c71e6d256e9
142 cndiff
144 cndiff
143
145
144 diff -r ef63ca68695b -r 40a904bbbe4c c
146 diff -r ef63ca68695b -r 40a904bbbe4c c
145 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
147 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
146 +++ b/c Thu Jan 01 00:00:01 1970 +0000
148 +++ b/c Thu Jan 01 00:00:01 1970 +0000
147 @@ -0,0 +1,2 @@
149 @@ -0,0 +1,2 @@
148 +$Id$
150 +$Id$
149 +tests for different changenodes
151 +tests for different changenodes
150 % qpop
152 % qpop
151 popping mqtest.diff
153 popping mqtest.diff
152 patch queue now empty
154 patch queue now empty
153 % qgoto - should imply qpush
155 % qgoto - should imply qpush
154 applying mqtest.diff
156 applying mqtest.diff
155 now at: mqtest.diff
157 now at: mqtest.diff
156 % cat
158 % cat
157 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
159 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
158 tests for different changenodes
160 tests for different changenodes
159 % qpop and move on
161 % qpop and move on
160 popping mqtest.diff
162 popping mqtest.diff
161 patch queue now empty
163 patch queue now empty
162 % copy
164 % copy
163 % kwfiles added
165 % kwfiles added
164 a
166 a
165 c
167 c
166 % commit
168 % commit
167 c
169 c
168 c: copy a:0045e12f6c5791aac80ca6cbfd97709a88307292
170 c: copy a:0045e12f6c5791aac80ca6cbfd97709a88307292
169 overwriting c expanding keywords
171 overwriting c expanding keywords
170 committed changeset 2:e22d299ac0c2bd8897b3df5114374b9e4d4ca62f
172 committed changeset 2:e22d299ac0c2bd8897b3df5114374b9e4d4ca62f
171 % cat a c
173 % cat a c
172 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 $
173 do not process $Id:
175 do not process $Id:
174 xxx $
176 xxx $
175 expand $Id: c,v e22d299ac0c2 1970/01/01 00:00:01 user $
177 expand $Id: c,v e22d299ac0c2 1970/01/01 00:00:01 user $
176 do not process $Id:
178 do not process $Id:
177 xxx $
179 xxx $
178 % touch copied c
180 % touch copied c
179 % status
181 % status
180 % kwfiles
182 % kwfiles
181 a
183 a
182 c
184 c
183 % ignored files
185 % ignored files
184 I b
186 I b
185 I sym
187 I sym
186 % all files
188 % all files
187 K a
189 K a
188 K c
190 K c
189 I b
191 I b
190 I sym
192 I sym
191 % diff --rev
193 % diff --rev
192 diff -r ef63ca68695b c
194 diff -r ef63ca68695b c
193 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
195 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
194 @@ -0,0 +1,3 @@
196 @@ -0,0 +1,3 @@
195 +expand $Id$
197 +expand $Id$
196 +do not process $Id:
198 +do not process $Id:
197 +xxx $
199 +xxx $
198 % rollback
200 % rollback
199 rolling back last transaction
201 rolling back last transaction
200 % status
202 % status
201 A c
203 A c
202 % update -C
204 % update -C
203 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
204 % custom keyword expansion
206 % custom keyword expansion
205 % try with kwdemo
207 % try with kwdemo
206 [extensions]
208 [extensions]
207 hgext.keyword =
209 hgext.keyword =
208 [keyword]
210 [keyword]
209 * =
211 * =
210 b = ignore
212 b = ignore
211 demo.txt =
213 demo.txt =
212 [keywordmaps]
214 [keywordmaps]
213 Xinfo = {author}: {desc}
215 Xinfo = {author}: {desc}
214 $Xinfo: test: hg keyword config and expansion example $
216 $Xinfo: test: hg keyword config and expansion example $
215 % cat
217 % cat
216 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 $
217 do not process $Id:
219 do not process $Id:
218 xxx $
220 xxx $
219 ignore $Id$
221 ignore $Id$
220 % hg cat
222 % hg cat
221 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 $
222 do not process $Id:
224 do not process $Id:
223 xxx $
225 xxx $
224 ignore $Id$
226 ignore $Id$
225 a
227 a
226 % interrupted commit should not change state
228 % interrupted commit should not change state
227 abort: empty commit message
229 abort: empty commit message
228 % status
230 % status
229 M a
231 M a
230 ? c
232 ? c
231 ? log
233 ? log
232 % commit
234 % commit
233 a
235 a
234 overwriting a expanding keywords
236 overwriting a expanding keywords
235 committed changeset 2:bb948857c743469b22bbf51f7ec8112279ca5d83
237 committed changeset 2:bb948857c743469b22bbf51f7ec8112279ca5d83
236 % status
238 % status
237 ? c
239 ? c
238 % verify
240 % verify
239 checking changesets
241 checking changesets
240 checking manifests
242 checking manifests
241 crosschecking files in changesets and manifests
243 crosschecking files in changesets and manifests
242 checking files
244 checking files
243 3 files, 3 changesets, 4 total revisions
245 3 files, 3 changesets, 4 total revisions
244 % cat
246 % cat
245 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 $
246 do not process $Id:
248 do not process $Id:
247 xxx $
249 xxx $
248 $Xinfo: User Name <user@example.com>: firstline $
250 $Xinfo: User Name <user@example.com>: firstline $
249 ignore $Id$
251 ignore $Id$
250 % hg cat
252 % hg cat
251 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 $
252 do not process $Id:
254 do not process $Id:
253 xxx $
255 xxx $
254 $Xinfo: User Name <user@example.com>: firstline $
256 $Xinfo: User Name <user@example.com>: firstline $
255 ignore $Id$
257 ignore $Id$
256 a
258 a
257 % annotate
259 % annotate
258 1: expand $Id$
260 1: expand $Id$
259 1: do not process $Id:
261 1: do not process $Id:
260 1: xxx $
262 1: xxx $
261 2: $Xinfo$
263 2: $Xinfo$
262 % remove
264 % remove
263 committed changeset 3:d14c712653769de926994cf7fbb06c8fbd68f012
265 committed changeset 3:d14c712653769de926994cf7fbb06c8fbd68f012
264 % status
266 % status
265 ? c
267 ? c
266 % rollback
268 % rollback
267 rolling back last transaction
269 rolling back last transaction
268 % status
270 % status
269 R a
271 R a
270 ? c
272 ? c
271 % revert a
273 % revert a
272 % cat a
274 % cat a
273 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 $
274 do not process $Id:
276 do not process $Id:
275 xxx $
277 xxx $
276 $Xinfo: User Name <user@example.com>: firstline $
278 $Xinfo: User Name <user@example.com>: firstline $
277 % clone to test incoming
279 % clone to test incoming
278 requesting all changes
280 requesting all changes
279 adding changesets
281 adding changesets
280 adding manifests
282 adding manifests
281 adding file changes
283 adding file changes
282 added 2 changesets with 3 changes to 3 files
284 added 2 changesets with 3 changes to 3 files
283 updating to branch default
285 updating to branch default
284 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
285 % incoming
287 % incoming
286 comparing with test-keyword/Test
288 comparing with test-keyword/Test
287 searching for changes
289 searching for changes
288 changeset: 2:bb948857c743
290 changeset: 2:bb948857c743
289 tag: tip
291 tag: tip
290 user: User Name <user@example.com>
292 user: User Name <user@example.com>
291 date: Thu Jan 01 00:00:02 1970 +0000
293 date: Thu Jan 01 00:00:02 1970 +0000
292 summary: firstline
294 summary: firstline
293
295
294 % commit rejecttest
296 % commit rejecttest
295 a
297 a
296 overwriting a expanding keywords
298 overwriting a expanding keywords
297 committed changeset 2:85e279d709ffc28c9fdd1b868570985fc3d87082
299 committed changeset 2:85e279d709ffc28c9fdd1b868570985fc3d87082
298 % export
300 % export
299 % import
301 % import
300 applying ../rejecttest.diff
302 applying ../rejecttest.diff
301 % cat
303 % cat
302 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
303 do not process $Id: rejecttest
305 do not process $Id: rejecttest
304 xxx $
306 xxx $
305 $Xinfo: User Name <user@example.com>: rejects? $
307 $Xinfo: User Name <user@example.com>: rejects? $
306 ignore $Id$
308 ignore $Id$
307
309
308 % rollback
310 % rollback
309 rolling back last transaction
311 rolling back last transaction
310 % clean update
312 % clean update
311 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
312 % kwexpand/kwshrink on selected files
314 % kwexpand/kwshrink on selected files
313 % copy a x/a
315 % copy a x/a
314 % kwexpand a
316 % kwexpand a
315 overwriting a expanding keywords
317 overwriting a expanding keywords
316 % kwexpand x/a should abort
318 % kwexpand x/a should abort
317 abort: outstanding uncommitted changes
319 abort: outstanding uncommitted changes
318 x/a
320 x/a
319 x/a: copy a:779c764182ce5d43e2b1eb66ce06d7b47bfe342e
321 x/a: copy a:779c764182ce5d43e2b1eb66ce06d7b47bfe342e
320 overwriting x/a expanding keywords
322 overwriting x/a expanding keywords
321 committed changeset 3:cfa68229c1167443337266ebac453c73b1d5d16e
323 committed changeset 3:cfa68229c1167443337266ebac453c73b1d5d16e
322 % cat a
324 % cat a
323 expand $Id: x/a cfa68229c116 Thu, 01 Jan 1970 00:00:03 +0000 user $
325 expand $Id: x/a cfa68229c116 Thu, 01 Jan 1970 00:00:03 +0000 user $
324 do not process $Id:
326 do not process $Id:
325 xxx $
327 xxx $
326 $Xinfo: User Name <user@example.com>: xa $
328 $Xinfo: User Name <user@example.com>: xa $
327 % kwshrink a inside directory x
329 % kwshrink a inside directory x
328 overwriting x/a shrinking keywords
330 overwriting x/a shrinking keywords
329 % cat a
331 % cat a
330 expand $Id$
332 expand $Id$
331 do not process $Id:
333 do not process $Id:
332 xxx $
334 xxx $
333 $Xinfo$
335 $Xinfo$
334 % kwexpand nonexistent
336 % kwexpand nonexistent
335 nonexistent:
337 nonexistent:
336 % hg serve
338 % hg serve
337 % expansion
339 % expansion
338 % hgweb file
340 % hgweb file
339 200 Script output follows
341 200 Script output follows
340
342
341 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 $
342 do not process $Id:
344 do not process $Id:
343 xxx $
345 xxx $
344 $Xinfo: User Name <user@example.com>: firstline $
346 $Xinfo: User Name <user@example.com>: firstline $
345 % no expansion
347 % no expansion
346 % hgweb annotate
348 % hgweb annotate
347 200 Script output follows
349 200 Script output follows
348
350
349
351
350 user@1: expand $Id$
352 user@1: expand $Id$
351 user@1: do not process $Id:
353 user@1: do not process $Id:
352 user@1: xxx $
354 user@1: xxx $
353 user@2: $Xinfo$
355 user@2: $Xinfo$
354
356
355
357
356
358
357
359
358 % hgweb changeset
360 % hgweb changeset
359 200 Script output follows
361 200 Script output follows
360
362
361
363
362 # HG changeset patch
364 # HG changeset patch
363 # User User Name <user@example.com>
365 # User User Name <user@example.com>
364 # Date 3 0
366 # Date 3 0
365 # Node ID cfa68229c1167443337266ebac453c73b1d5d16e
367 # Node ID cfa68229c1167443337266ebac453c73b1d5d16e
366 # Parent bb948857c743469b22bbf51f7ec8112279ca5d83
368 # Parent bb948857c743469b22bbf51f7ec8112279ca5d83
367 xa
369 xa
368
370
369 diff -r bb948857c743 -r cfa68229c116 x/a
371 diff -r bb948857c743 -r cfa68229c116 x/a
370 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
372 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
371 +++ b/x/a Thu Jan 01 00:00:03 1970 +0000
373 +++ b/x/a Thu Jan 01 00:00:03 1970 +0000
372 @@ -0,0 +1,4 @@
374 @@ -0,0 +1,4 @@
373 +expand $Id$
375 +expand $Id$
374 +do not process $Id:
376 +do not process $Id:
375 +xxx $
377 +xxx $
376 +$Xinfo$
378 +$Xinfo$
377
379
378 % hgweb filediff
380 % hgweb filediff
379 200 Script output follows
381 200 Script output follows
380
382
381
383
382 diff -r ef63ca68695b -r bb948857c743 a
384 diff -r ef63ca68695b -r bb948857c743 a
383 --- a/a Thu Jan 01 00:00:00 1970 +0000
385 --- a/a Thu Jan 01 00:00:00 1970 +0000
384 +++ b/a Thu Jan 01 00:00:02 1970 +0000
386 +++ b/a Thu Jan 01 00:00:02 1970 +0000
385 @@ -1,3 +1,4 @@
387 @@ -1,3 +1,4 @@
386 expand $Id$
388 expand $Id$
387 do not process $Id:
389 do not process $Id:
388 xxx $
390 xxx $
389 +$Xinfo$
391 +$Xinfo$
390
392
391
393
392
394
393
395
394 % errors encountered
396 % errors encountered
395 % merge/resolve
397 % merge/resolve
396 % simplemerge
398 % simplemerge
397 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
398 created new head
400 created new head
399 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
400 (branch merge, don't forget to commit)
402 (branch merge, don't forget to commit)
401 $Id: m 8731e1dadc99 Thu, 01 Jan 1970 00:00:00 +0000 test $
403 $Id: m 8731e1dadc99 Thu, 01 Jan 1970 00:00:00 +0000 test $
402 foo
404 foo
403 % conflict
405 % conflict
404 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
405 created new head
407 created new head
406 merging m
408 merging m
407 warning: conflicts during merge.
409 warning: conflicts during merge.
408 merging m failed!
410 merging m failed!
409 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
410 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
411 % keyword stays outside conflict zone
413 % keyword stays outside conflict zone
412 $Id$
414 $Id$
413 <<<<<<< local
415 <<<<<<< local
414 bar
416 bar
415 =======
417 =======
416 foo
418 foo
417 >>>>>>> other
419 >>>>>>> other
418 % resolve to local
420 % resolve to local
419 $Id: m 43dfd2854b5b Thu, 01 Jan 1970 00:00:00 +0000 test $
421 $Id: m 43dfd2854b5b Thu, 01 Jan 1970 00:00:00 +0000 test $
420 bar
422 bar
421 % switch off expansion
423 % switch off expansion
422 % kwshrink with unknown file u
424 % kwshrink with unknown file u
423 overwriting a shrinking keywords
425 overwriting a shrinking keywords
424 overwriting m shrinking keywords
426 overwriting m shrinking keywords
425 overwriting x/a shrinking keywords
427 overwriting x/a shrinking keywords
426 % cat
428 % cat
427 expand $Id$
429 expand $Id$
428 do not process $Id:
430 do not process $Id:
429 xxx $
431 xxx $
430 $Xinfo$
432 $Xinfo$
431 ignore $Id$
433 ignore $Id$
432 % hg cat
434 % hg cat
433 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
435 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
434 do not process $Id:
436 do not process $Id:
435 xxx $
437 xxx $
436 $Xinfo: User Name <user@example.com>: firstline $
438 $Xinfo: User Name <user@example.com>: firstline $
437 ignore $Id$
439 ignore $Id$
438 a
440 a
439 % cat
441 % cat
440 expand $Id$
442 expand $Id$
441 do not process $Id:
443 do not process $Id:
442 xxx $
444 xxx $
443 $Xinfo$
445 $Xinfo$
444 ignore $Id$
446 ignore $Id$
445 % hg cat
447 % hg cat
446 expand $Id$
448 expand $Id$
447 do not process $Id:
449 do not process $Id:
448 xxx $
450 xxx $
449 $Xinfo$
451 $Xinfo$
450 ignore $Id$
452 ignore $Id$
451 a
453 a
General Comments 0
You need to be logged in to leave comments. Login now