##// END OF EJS Templates
fixed typos found in translatable strings...
Martin Geisler -
r8668:aea3a231 default
parent child Browse files
Show More
@@ -1,270 +1,270 b''
1 # color.py color output for the status and qseries commands
1 # color.py color output for the status and qseries commands
2 #
2 #
3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
4 #
4 #
5 # This program is free software; you can redistribute it and/or modify it
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the
6 # under the terms of the GNU General Public License as published by the
7 # Free Software Foundation; either version 2 of the License, or (at your
7 # Free Software Foundation; either version 2 of the License, or (at your
8 # option) any later version.
8 # option) any later version.
9 #
9 #
10 # This program is distributed in the hope that it will be useful, but
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 # Public License for more details.
13 # Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License along
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
18
19 '''add color output to status, qseries, and diff-related commands
19 '''add color output to status, qseries, and diff-related commands
20
20
21 This extension modifies the status command to add color to its output
21 This extension modifies the status command to add color to its output
22 to reflect file status, the qseries command to add color to reflect
22 to reflect file status, the qseries command to add color to reflect
23 patch status (applied, unapplied, missing), and to diff-related
23 patch status (applied, unapplied, missing), and to diff-related
24 commands to highlight additions, removals, diff headers, and trailing
24 commands to highlight additions, removals, diff headers, and trailing
25 whitespace.
25 whitespace.
26
26
27 Other effects in addition to color, like bold and underlined text, are
27 Other effects in addition to color, like bold and underlined text, are
28 also available. Effects are rendered with the ECMA-48 SGR control
28 also available. Effects are rendered with the ECMA-48 SGR control
29 function (aka ANSI escape codes). This module also provides the
29 function (aka ANSI escape codes). This module also provides the
30 render_text function, which can be used to add effects to any text.
30 render_text function, which can be used to add effects to any text.
31
31
32 To enable this extension, add this to your .hgrc file:
32 To enable this extension, add this to your .hgrc file:
33 [extensions]
33 [extensions]
34 color =
34 color =
35
35
36 Default effects my be overriden from the .hgrc file:
36 Default effects my be overridden from the .hgrc file:
37
37
38 [color]
38 [color]
39 status.modified = blue bold underline red_background
39 status.modified = blue bold underline red_background
40 status.added = green bold
40 status.added = green bold
41 status.removed = red bold blue_background
41 status.removed = red bold blue_background
42 status.deleted = cyan bold underline
42 status.deleted = cyan bold underline
43 status.unknown = magenta bold underline
43 status.unknown = magenta bold underline
44 status.ignored = black bold
44 status.ignored = black bold
45
45
46 # 'none' turns off all effects
46 # 'none' turns off all effects
47 status.clean = none
47 status.clean = none
48 status.copied = none
48 status.copied = none
49
49
50 qseries.applied = blue bold underline
50 qseries.applied = blue bold underline
51 qseries.unapplied = black bold
51 qseries.unapplied = black bold
52 qseries.missing = red bold
52 qseries.missing = red bold
53
53
54 diff.diffline = bold
54 diff.diffline = bold
55 diff.extended = cyan bold
55 diff.extended = cyan bold
56 diff.file_a = red bold
56 diff.file_a = red bold
57 diff.file_b = green bold
57 diff.file_b = green bold
58 diff.hunk = magenta
58 diff.hunk = magenta
59 diff.deleted = red
59 diff.deleted = red
60 diff.inserted = green
60 diff.inserted = green
61 diff.changed = white
61 diff.changed = white
62 diff.trailingwhitespace = bold red_background
62 diff.trailingwhitespace = bold red_background
63 '''
63 '''
64
64
65 import os, sys
65 import os, sys
66
66
67 from mercurial import cmdutil, commands, extensions
67 from mercurial import cmdutil, commands, extensions
68 from mercurial.i18n import _
68 from mercurial.i18n import _
69
69
70 # start and stop parameters for effects
70 # start and stop parameters for effects
71 _effect_params = {'none': 0,
71 _effect_params = {'none': 0,
72 'black': 30,
72 'black': 30,
73 'red': 31,
73 'red': 31,
74 'green': 32,
74 'green': 32,
75 'yellow': 33,
75 'yellow': 33,
76 'blue': 34,
76 'blue': 34,
77 'magenta': 35,
77 'magenta': 35,
78 'cyan': 36,
78 'cyan': 36,
79 'white': 37,
79 'white': 37,
80 'bold': 1,
80 'bold': 1,
81 'italic': 3,
81 'italic': 3,
82 'underline': 4,
82 'underline': 4,
83 'inverse': 7,
83 'inverse': 7,
84 'black_background': 40,
84 'black_background': 40,
85 'red_background': 41,
85 'red_background': 41,
86 'green_background': 42,
86 'green_background': 42,
87 'yellow_background': 43,
87 'yellow_background': 43,
88 'blue_background': 44,
88 'blue_background': 44,
89 'purple_background': 45,
89 'purple_background': 45,
90 'cyan_background': 46,
90 'cyan_background': 46,
91 'white_background': 47}
91 'white_background': 47}
92
92
93 def render_effects(text, effects):
93 def render_effects(text, effects):
94 'Wrap text in commands to turn on each effect.'
94 'Wrap text in commands to turn on each effect.'
95 start = [str(_effect_params[e]) for e in ['none'] + effects]
95 start = [str(_effect_params[e]) for e in ['none'] + effects]
96 start = '\033[' + ';'.join(start) + 'm'
96 start = '\033[' + ';'.join(start) + 'm'
97 stop = '\033[' + str(_effect_params['none']) + 'm'
97 stop = '\033[' + str(_effect_params['none']) + 'm'
98 return ''.join([start, text, stop])
98 return ''.join([start, text, stop])
99
99
100 def colorstatus(orig, ui, repo, *pats, **opts):
100 def colorstatus(orig, ui, repo, *pats, **opts):
101 '''run the status command with colored output'''
101 '''run the status command with colored output'''
102
102
103 delimiter = opts['print0'] and '\0' or '\n'
103 delimiter = opts['print0'] and '\0' or '\n'
104
104
105 nostatus = opts.get('no_status')
105 nostatus = opts.get('no_status')
106 opts['no_status'] = False
106 opts['no_status'] = False
107 # run status and capture its output
107 # run status and capture its output
108 ui.pushbuffer()
108 ui.pushbuffer()
109 retval = orig(ui, repo, *pats, **opts)
109 retval = orig(ui, repo, *pats, **opts)
110 # filter out empty strings
110 # filter out empty strings
111 lines_with_status = [ line for line in ui.popbuffer().split(delimiter) if line ]
111 lines_with_status = [ line for line in ui.popbuffer().split(delimiter) if line ]
112
112
113 if nostatus:
113 if nostatus:
114 lines = [l[2:] for l in lines_with_status]
114 lines = [l[2:] for l in lines_with_status]
115 else:
115 else:
116 lines = lines_with_status
116 lines = lines_with_status
117
117
118 # apply color to output and display it
118 # apply color to output and display it
119 for i in xrange(len(lines)):
119 for i in xrange(len(lines)):
120 status = _status_abbreviations[lines_with_status[i][0]]
120 status = _status_abbreviations[lines_with_status[i][0]]
121 effects = _status_effects[status]
121 effects = _status_effects[status]
122 if effects:
122 if effects:
123 lines[i] = render_effects(lines[i], effects)
123 lines[i] = render_effects(lines[i], effects)
124 ui.write(lines[i] + delimiter)
124 ui.write(lines[i] + delimiter)
125 return retval
125 return retval
126
126
127 _status_abbreviations = { 'M': 'modified',
127 _status_abbreviations = { 'M': 'modified',
128 'A': 'added',
128 'A': 'added',
129 'R': 'removed',
129 'R': 'removed',
130 '!': 'deleted',
130 '!': 'deleted',
131 '?': 'unknown',
131 '?': 'unknown',
132 'I': 'ignored',
132 'I': 'ignored',
133 'C': 'clean',
133 'C': 'clean',
134 ' ': 'copied', }
134 ' ': 'copied', }
135
135
136 _status_effects = { 'modified': ['blue', 'bold'],
136 _status_effects = { 'modified': ['blue', 'bold'],
137 'added': ['green', 'bold'],
137 'added': ['green', 'bold'],
138 'removed': ['red', 'bold'],
138 'removed': ['red', 'bold'],
139 'deleted': ['cyan', 'bold', 'underline'],
139 'deleted': ['cyan', 'bold', 'underline'],
140 'unknown': ['magenta', 'bold', 'underline'],
140 'unknown': ['magenta', 'bold', 'underline'],
141 'ignored': ['black', 'bold'],
141 'ignored': ['black', 'bold'],
142 'clean': ['none'],
142 'clean': ['none'],
143 'copied': ['none'], }
143 'copied': ['none'], }
144
144
145 def colorqseries(orig, ui, repo, *dummy, **opts):
145 def colorqseries(orig, ui, repo, *dummy, **opts):
146 '''run the qseries command with colored output'''
146 '''run the qseries command with colored output'''
147 ui.pushbuffer()
147 ui.pushbuffer()
148 retval = orig(ui, repo, **opts)
148 retval = orig(ui, repo, **opts)
149 patches = ui.popbuffer().splitlines()
149 patches = ui.popbuffer().splitlines()
150 for patch in patches:
150 for patch in patches:
151 patchname = patch
151 patchname = patch
152 if opts['summary']:
152 if opts['summary']:
153 patchname = patchname.split(': ')[0]
153 patchname = patchname.split(': ')[0]
154 if ui.verbose:
154 if ui.verbose:
155 patchname = patchname.split(' ', 2)[-1]
155 patchname = patchname.split(' ', 2)[-1]
156
156
157 if opts['missing']:
157 if opts['missing']:
158 effects = _patch_effects['missing']
158 effects = _patch_effects['missing']
159 # Determine if patch is applied.
159 # Determine if patch is applied.
160 elif [ applied for applied in repo.mq.applied
160 elif [ applied for applied in repo.mq.applied
161 if patchname == applied.name ]:
161 if patchname == applied.name ]:
162 effects = _patch_effects['applied']
162 effects = _patch_effects['applied']
163 else:
163 else:
164 effects = _patch_effects['unapplied']
164 effects = _patch_effects['unapplied']
165 ui.write(render_effects(patch, effects) + '\n')
165 ui.write(render_effects(patch, effects) + '\n')
166 return retval
166 return retval
167
167
168 _patch_effects = { 'applied': ['blue', 'bold', 'underline'],
168 _patch_effects = { 'applied': ['blue', 'bold', 'underline'],
169 'missing': ['red', 'bold'],
169 'missing': ['red', 'bold'],
170 'unapplied': ['black', 'bold'], }
170 'unapplied': ['black', 'bold'], }
171
171
172 def colorwrap(orig, s):
172 def colorwrap(orig, s):
173 '''wrap ui.write for colored diff output'''
173 '''wrap ui.write for colored diff output'''
174 lines = s.split('\n')
174 lines = s.split('\n')
175 for i, line in enumerate(lines):
175 for i, line in enumerate(lines):
176 stripline = line
176 stripline = line
177 if line and line[0] in '+-':
177 if line and line[0] in '+-':
178 # highlight trailing whitespace, but only in changed lines
178 # highlight trailing whitespace, but only in changed lines
179 stripline = line.rstrip()
179 stripline = line.rstrip()
180 for prefix, style in _diff_prefixes:
180 for prefix, style in _diff_prefixes:
181 if stripline.startswith(prefix):
181 if stripline.startswith(prefix):
182 lines[i] = render_effects(stripline, _diff_effects[style])
182 lines[i] = render_effects(stripline, _diff_effects[style])
183 break
183 break
184 if line != stripline:
184 if line != stripline:
185 lines[i] += render_effects(
185 lines[i] += render_effects(
186 line[len(stripline):], _diff_effects['trailingwhitespace'])
186 line[len(stripline):], _diff_effects['trailingwhitespace'])
187 orig('\n'.join(lines))
187 orig('\n'.join(lines))
188
188
189 def colorshowpatch(orig, self, node):
189 def colorshowpatch(orig, self, node):
190 '''wrap cmdutil.changeset_printer.showpatch with colored output'''
190 '''wrap cmdutil.changeset_printer.showpatch with colored output'''
191 oldwrite = extensions.wrapfunction(self.ui, 'write', colorwrap)
191 oldwrite = extensions.wrapfunction(self.ui, 'write', colorwrap)
192 try:
192 try:
193 orig(self, node)
193 orig(self, node)
194 finally:
194 finally:
195 self.ui.write = oldwrite
195 self.ui.write = oldwrite
196
196
197 def colordiff(orig, ui, repo, *pats, **opts):
197 def colordiff(orig, ui, repo, *pats, **opts):
198 '''run the diff command with colored output'''
198 '''run the diff command with colored output'''
199 oldwrite = extensions.wrapfunction(ui, 'write', colorwrap)
199 oldwrite = extensions.wrapfunction(ui, 'write', colorwrap)
200 try:
200 try:
201 orig(ui, repo, *pats, **opts)
201 orig(ui, repo, *pats, **opts)
202 finally:
202 finally:
203 ui.write = oldwrite
203 ui.write = oldwrite
204
204
205 _diff_prefixes = [('diff', 'diffline'),
205 _diff_prefixes = [('diff', 'diffline'),
206 ('copy', 'extended'),
206 ('copy', 'extended'),
207 ('rename', 'extended'),
207 ('rename', 'extended'),
208 ('old', 'extended'),
208 ('old', 'extended'),
209 ('new', 'extended'),
209 ('new', 'extended'),
210 ('deleted', 'extended'),
210 ('deleted', 'extended'),
211 ('---', 'file_a'),
211 ('---', 'file_a'),
212 ('+++', 'file_b'),
212 ('+++', 'file_b'),
213 ('@', 'hunk'),
213 ('@', 'hunk'),
214 ('-', 'deleted'),
214 ('-', 'deleted'),
215 ('+', 'inserted')]
215 ('+', 'inserted')]
216
216
217 _diff_effects = {'diffline': ['bold'],
217 _diff_effects = {'diffline': ['bold'],
218 'extended': ['cyan', 'bold'],
218 'extended': ['cyan', 'bold'],
219 'file_a': ['red', 'bold'],
219 'file_a': ['red', 'bold'],
220 'file_b': ['green', 'bold'],
220 'file_b': ['green', 'bold'],
221 'hunk': ['magenta'],
221 'hunk': ['magenta'],
222 'deleted': ['red'],
222 'deleted': ['red'],
223 'inserted': ['green'],
223 'inserted': ['green'],
224 'changed': ['white'],
224 'changed': ['white'],
225 'trailingwhitespace': ['bold', 'red_background']}
225 'trailingwhitespace': ['bold', 'red_background']}
226
226
227 def uisetup(ui):
227 def uisetup(ui):
228 '''Initialize the extension.'''
228 '''Initialize the extension.'''
229 _setupcmd(ui, 'diff', commands.table, colordiff, _diff_effects)
229 _setupcmd(ui, 'diff', commands.table, colordiff, _diff_effects)
230 _setupcmd(ui, 'incoming', commands.table, None, _diff_effects)
230 _setupcmd(ui, 'incoming', commands.table, None, _diff_effects)
231 _setupcmd(ui, 'log', commands.table, None, _diff_effects)
231 _setupcmd(ui, 'log', commands.table, None, _diff_effects)
232 _setupcmd(ui, 'outgoing', commands.table, None, _diff_effects)
232 _setupcmd(ui, 'outgoing', commands.table, None, _diff_effects)
233 _setupcmd(ui, 'tip', commands.table, None, _diff_effects)
233 _setupcmd(ui, 'tip', commands.table, None, _diff_effects)
234 _setupcmd(ui, 'status', commands.table, colorstatus, _status_effects)
234 _setupcmd(ui, 'status', commands.table, colorstatus, _status_effects)
235 try:
235 try:
236 mq = extensions.find('mq')
236 mq = extensions.find('mq')
237 _setupcmd(ui, 'qdiff', mq.cmdtable, colordiff, _diff_effects)
237 _setupcmd(ui, 'qdiff', mq.cmdtable, colordiff, _diff_effects)
238 _setupcmd(ui, 'qseries', mq.cmdtable, colorqseries, _patch_effects)
238 _setupcmd(ui, 'qseries', mq.cmdtable, colorqseries, _patch_effects)
239 except KeyError:
239 except KeyError:
240 # The mq extension is not enabled
240 # The mq extension is not enabled
241 pass
241 pass
242
242
243 def _setupcmd(ui, cmd, table, func, effectsmap):
243 def _setupcmd(ui, cmd, table, func, effectsmap):
244 '''patch in command to command table and load effect map'''
244 '''patch in command to command table and load effect map'''
245 def nocolor(orig, *args, **opts):
245 def nocolor(orig, *args, **opts):
246
246
247 if (opts['no_color'] or opts['color'] == 'never' or
247 if (opts['no_color'] or opts['color'] == 'never' or
248 (opts['color'] == 'auto' and (os.environ.get('TERM') == 'dumb'
248 (opts['color'] == 'auto' and (os.environ.get('TERM') == 'dumb'
249 or not sys.__stdout__.isatty()))):
249 or not sys.__stdout__.isatty()))):
250 return orig(*args, **opts)
250 return orig(*args, **opts)
251
251
252 oldshowpatch = extensions.wrapfunction(cmdutil.changeset_printer,
252 oldshowpatch = extensions.wrapfunction(cmdutil.changeset_printer,
253 'showpatch', colorshowpatch)
253 'showpatch', colorshowpatch)
254 try:
254 try:
255 if func is not None:
255 if func is not None:
256 return func(orig, *args, **opts)
256 return func(orig, *args, **opts)
257 return orig(*args, **opts)
257 return orig(*args, **opts)
258 finally:
258 finally:
259 cmdutil.changeset_printer.showpatch = oldshowpatch
259 cmdutil.changeset_printer.showpatch = oldshowpatch
260
260
261 entry = extensions.wrapcommand(table, cmd, nocolor)
261 entry = extensions.wrapcommand(table, cmd, nocolor)
262 entry[1].extend([
262 entry[1].extend([
263 ('', 'color', 'auto', _("when to colorize (always, auto, or never)")),
263 ('', 'color', 'auto', _("when to colorize (always, auto, or never)")),
264 ('', 'no-color', None, _("don't colorize output")),
264 ('', 'no-color', None, _("don't colorize output")),
265 ])
265 ])
266
266
267 for status in effectsmap:
267 for status in effectsmap:
268 effects = ui.configlist('color', cmd + '.' + status)
268 effects = ui.configlist('color', cmd + '.' + status)
269 if effects:
269 if effects:
270 effectsmap[status] = effects
270 effectsmap[status] = effects
@@ -1,274 +1,274 b''
1 # convert.py Foreign SCM converter
1 # convert.py Foreign SCM converter
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.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 '''converting foreign VCS repositories to Mercurial'''
8 '''converting foreign VCS repositories to Mercurial'''
9
9
10 import convcmd
10 import convcmd
11 import cvsps
11 import cvsps
12 import subversion
12 import subversion
13 from mercurial import commands
13 from mercurial import commands
14 from mercurial.i18n import _
14 from mercurial.i18n import _
15
15
16 # Commands definition was moved elsewhere to ease demandload job.
16 # Commands definition was moved elsewhere to ease demandload job.
17
17
18 def convert(ui, src, dest=None, revmapfile=None, **opts):
18 def convert(ui, src, dest=None, revmapfile=None, **opts):
19 """convert a foreign SCM repository to a Mercurial one.
19 """convert a foreign SCM repository to a Mercurial one.
20
20
21 Accepted source formats [identifiers]:
21 Accepted source formats [identifiers]:
22 - Mercurial [hg]
22 - Mercurial [hg]
23 - CVS [cvs]
23 - CVS [cvs]
24 - Darcs [darcs]
24 - Darcs [darcs]
25 - git [git]
25 - git [git]
26 - Subversion [svn]
26 - Subversion [svn]
27 - Monotone [mtn]
27 - Monotone [mtn]
28 - GNU Arch [gnuarch]
28 - GNU Arch [gnuarch]
29 - Bazaar [bzr]
29 - Bazaar [bzr]
30 - Perforce [p4]
30 - Perforce [p4]
31
31
32 Accepted destination formats [identifiers]:
32 Accepted destination formats [identifiers]:
33 - Mercurial [hg]
33 - Mercurial [hg]
34 - Subversion [svn] (history on branches is not preserved)
34 - Subversion [svn] (history on branches is not preserved)
35
35
36 If no revision is given, all revisions will be converted.
36 If no revision is given, all revisions will be converted.
37 Otherwise, convert will only import up to the named revision
37 Otherwise, convert will only import up to the named revision
38 (given in a format understood by the source).
38 (given in a format understood by the source).
39
39
40 If no destination directory name is specified, it defaults to the
40 If no destination directory name is specified, it defaults to the
41 basename of the source with '-hg' appended. If the destination
41 basename of the source with '-hg' appended. If the destination
42 repository doesn't exist, it will be created.
42 repository doesn't exist, it will be created.
43
43
44 If <REVMAP> isn't given, it will be put in a default location
44 If <REVMAP> isn't given, it will be put in a default location
45 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file
45 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file
46 that maps each source commit ID to the destination ID for that
46 that maps each source commit ID to the destination ID for that
47 revision, like so:
47 revision, like so:
48 <source ID> <destination ID>
48 <source ID> <destination ID>
49
49
50 If the file doesn't exist, it's automatically created. It's
50 If the file doesn't exist, it's automatically created. It's
51 updated on each commit copied, so convert-repo can be interrupted
51 updated on each commit copied, so convert-repo can be interrupted
52 and can be run repeatedly to copy new commits.
52 and can be run repeatedly to copy new commits.
53
53
54 The [username mapping] file is a simple text file that maps each
54 The [username mapping] file is a simple text file that maps each
55 source commit author to a destination commit author. It is handy
55 source commit author to a destination commit author. It is handy
56 for source SCMs that use unix logins to identify authors (eg:
56 for source SCMs that use unix logins to identify authors (eg:
57 CVS). One line per author mapping and the line format is:
57 CVS). One line per author mapping and the line format is:
58 srcauthor=whatever string you want
58 srcauthor=whatever string you want
59
59
60 The filemap is a file that allows filtering and remapping of files
60 The filemap is a file that allows filtering and remapping of files
61 and directories. Comment lines start with '#'. Each line can
61 and directories. Comment lines start with '#'. Each line can
62 contain one of the following directives:
62 contain one of the following directives:
63
63
64 include path/to/file
64 include path/to/file
65
65
66 exclude path/to/file
66 exclude path/to/file
67
67
68 rename from/file to/file
68 rename from/file to/file
69
69
70 The 'include' directive causes a file, or all files under a
70 The 'include' directive causes a file, or all files under a
71 directory, to be included in the destination repository, and the
71 directory, to be included in the destination repository, and the
72 exclusion of all other files and directories not explicitely included.
72 exclusion of all other files and directories not explicitly included.
73 The 'exclude' directive causes files or directories to be omitted.
73 The 'exclude' directive causes files or directories to be omitted.
74 The 'rename' directive renames a file or directory. To rename from
74 The 'rename' directive renames a file or directory. To rename from
75 a subdirectory into the root of the repository, use '.' as the
75 a subdirectory into the root of the repository, use '.' as the
76 path to rename to.
76 path to rename to.
77
77
78 The splicemap is a file that allows insertion of synthetic
78 The splicemap is a file that allows insertion of synthetic
79 history, letting you specify the parents of a revision. This is
79 history, letting you specify the parents of a revision. This is
80 useful if you want to e.g. give a Subversion merge two parents, or
80 useful if you want to e.g. give a Subversion merge two parents, or
81 graft two disconnected series of history together. Each entry
81 graft two disconnected series of history together. Each entry
82 contains a key, followed by a space, followed by one or two
82 contains a key, followed by a space, followed by one or two
83 comma-separated values. The key is the revision ID in the source
83 comma-separated values. The key is the revision ID in the source
84 revision control system whose parents should be modified (same
84 revision control system whose parents should be modified (same
85 format as a key in .hg/shamap). The values are the revision IDs
85 format as a key in .hg/shamap). The values are the revision IDs
86 (in either the source or destination revision control system) that
86 (in either the source or destination revision control system) that
87 should be used as the new parents for that node.
87 should be used as the new parents for that node.
88
88
89 The branchmap is a file that allows you to rename a branch when it is
89 The branchmap is a file that allows you to rename a branch when it is
90 being brought in from whatever external repository. When used in
90 being brought in from whatever external repository. When used in
91 conjunction with a splicemap, it allows for a powerful combination
91 conjunction with a splicemap, it allows for a powerful combination
92 to help fix even the most badly mismanaged repositories and turn them
92 to help fix even the most badly mismanaged repositories and turn them
93 into nicely structured Mercurial repositories. The branchmap contains
93 into nicely structured Mercurial repositories. The branchmap contains
94 lines of the form "original_branch_name new_branch_name".
94 lines of the form "original_branch_name new_branch_name".
95 "original_branch_name" is the name of the branch in the source
95 "original_branch_name" is the name of the branch in the source
96 repository, and "new_branch_name" is the name of the branch is the
96 repository, and "new_branch_name" is the name of the branch is the
97 destination repository. This can be used to (for instance) move code
97 destination repository. This can be used to (for instance) move code
98 in one repository from "default" to a named branch.
98 in one repository from "default" to a named branch.
99
99
100 Mercurial Source
100 Mercurial Source
101 -----------------
101 -----------------
102
102
103 --config convert.hg.ignoreerrors=False (boolean)
103 --config convert.hg.ignoreerrors=False (boolean)
104 ignore integrity errors when reading. Use it to fix Mercurial
104 ignore integrity errors when reading. Use it to fix Mercurial
105 repositories with missing revlogs, by converting from and to
105 repositories with missing revlogs, by converting from and to
106 Mercurial.
106 Mercurial.
107 --config convert.hg.saverev=False (boolean)
107 --config convert.hg.saverev=False (boolean)
108 store original revision ID in changeset (forces target IDs to
108 store original revision ID in changeset (forces target IDs to
109 change)
109 change)
110 --config convert.hg.startrev=0 (hg revision identifier)
110 --config convert.hg.startrev=0 (hg revision identifier)
111 convert start revision and its descendants
111 convert start revision and its descendants
112
112
113 CVS Source
113 CVS Source
114 ----------
114 ----------
115
115
116 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
116 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
117 to indicate the starting point of what will be converted. Direct
117 to indicate the starting point of what will be converted. Direct
118 access to the repository files is not needed, unless of course the
118 access to the repository files is not needed, unless of course the
119 repository is :local:. The conversion uses the top level directory
119 repository is :local:. The conversion uses the top level directory
120 in the sandbox to find the CVS repository, and then uses CVS rlog
120 in the sandbox to find the CVS repository, and then uses CVS rlog
121 commands to find files to convert. This means that unless a
121 commands to find files to convert. This means that unless a
122 filemap is given, all files under the starting directory will be
122 filemap is given, all files under the starting directory will be
123 converted, and that any directory reorganisation in the CVS
123 converted, and that any directory reorganization in the CVS
124 sandbox is ignored.
124 sandbox is ignored.
125
125
126 Because CVS does not have changesets, it is necessary to collect
126 Because CVS does not have changesets, it is necessary to collect
127 individual commits to CVS and merge them into changesets. CVS
127 individual commits to CVS and merge them into changesets. CVS
128 source uses its internal changeset merging code by default but can
128 source uses its internal changeset merging code by default but can
129 be configured to call the external 'cvsps' program by setting:
129 be configured to call the external 'cvsps' program by setting:
130 --config convert.cvsps='cvsps -A -u --cvs-direct -q'
130 --config convert.cvsps='cvsps -A -u --cvs-direct -q'
131 This option is deprecated and will be removed in Mercurial 1.4.
131 This option is deprecated and will be removed in Mercurial 1.4.
132
132
133 The options shown are the defaults.
133 The options shown are the defaults.
134
134
135 Internal cvsps is selected by setting
135 Internal cvsps is selected by setting
136 --config convert.cvsps=builtin
136 --config convert.cvsps=builtin
137 and has a few more configurable options:
137 and has a few more configurable options:
138 --config convert.cvsps.cache=True (boolean)
138 --config convert.cvsps.cache=True (boolean)
139 Set to False to disable remote log caching, for testing and
139 Set to False to disable remote log caching, for testing and
140 debugging purposes.
140 debugging purposes.
141 --config convert.cvsps.fuzz=60 (integer)
141 --config convert.cvsps.fuzz=60 (integer)
142 Specify the maximum time (in seconds) that is allowed
142 Specify the maximum time (in seconds) that is allowed
143 between commits with identical user and log message in a
143 between commits with identical user and log message in a
144 single changeset. When very large files were checked in as
144 single changeset. When very large files were checked in as
145 part of a changeset then the default may not be long
145 part of a changeset then the default may not be long
146 enough.
146 enough.
147 --config convert.cvsps.mergeto='{{mergetobranch ([-\\w]+)}}'
147 --config convert.cvsps.mergeto='{{mergetobranch ([-\\w]+)}}'
148 Specify a regular expression to which commit log messages
148 Specify a regular expression to which commit log messages
149 are matched. If a match occurs, then the conversion
149 are matched. If a match occurs, then the conversion
150 process will insert a dummy revision merging the branch on
150 process will insert a dummy revision merging the branch on
151 which this log message occurs to the branch indicated in
151 which this log message occurs to the branch indicated in
152 the regex.
152 the regex.
153 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\\w]+)}}'
153 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\\w]+)}}'
154 Specify a regular expression to which commit log messages
154 Specify a regular expression to which commit log messages
155 are matched. If a match occurs, then the conversion
155 are matched. If a match occurs, then the conversion
156 process will add the most recent revision on the branch
156 process will add the most recent revision on the branch
157 indicated in the regex as the second parent of the
157 indicated in the regex as the second parent of the
158 changeset.
158 changeset.
159
159
160 The hgext/convert/cvsps wrapper script allows the builtin
160 The hgext/convert/cvsps wrapper script allows the builtin
161 changeset merging code to be run without doing a conversion. Its
161 changeset merging code to be run without doing a conversion. Its
162 parameters and output are similar to that of cvsps 2.1.
162 parameters and output are similar to that of cvsps 2.1.
163
163
164 Subversion Source
164 Subversion Source
165 -----------------
165 -----------------
166
166
167 Subversion source detects classical trunk/branches/tags layouts.
167 Subversion source detects classical trunk/branches/tags layouts.
168 By default, the supplied "svn://repo/path/" source URL is
168 By default, the supplied "svn://repo/path/" source URL is
169 converted as a single branch. If "svn://repo/path/trunk" exists it
169 converted as a single branch. If "svn://repo/path/trunk" exists it
170 replaces the default branch. If "svn://repo/path/branches" exists,
170 replaces the default branch. If "svn://repo/path/branches" exists,
171 its subdirectories are listed as possible branches. If
171 its subdirectories are listed as possible branches. If
172 "svn://repo/path/tags" exists, it is looked for tags referencing
172 "svn://repo/path/tags" exists, it is looked for tags referencing
173 converted branches. Default "trunk", "branches" and "tags" values
173 converted branches. Default "trunk", "branches" and "tags" values
174 can be overriden with following options. Set them to paths
174 can be overridden with following options. Set them to paths
175 relative to the source URL, or leave them blank to disable
175 relative to the source URL, or leave them blank to disable auto
176 autodetection.
176 detection.
177
177
178 --config convert.svn.branches=branches (directory name)
178 --config convert.svn.branches=branches (directory name)
179 specify the directory containing branches
179 specify the directory containing branches
180 --config convert.svn.tags=tags (directory name)
180 --config convert.svn.tags=tags (directory name)
181 specify the directory containing tags
181 specify the directory containing tags
182 --config convert.svn.trunk=trunk (directory name)
182 --config convert.svn.trunk=trunk (directory name)
183 specify the name of the trunk branch
183 specify the name of the trunk branch
184
184
185 Source history can be retrieved starting at a specific revision,
185 Source history can be retrieved starting at a specific revision,
186 instead of being integrally converted. Only single branch
186 instead of being integrally converted. Only single branch
187 conversions are supported.
187 conversions are supported.
188
188
189 --config convert.svn.startrev=0 (svn revision number)
189 --config convert.svn.startrev=0 (svn revision number)
190 specify start Subversion revision.
190 specify start Subversion revision.
191
191
192 Perforce Source
192 Perforce Source
193 ---------------
193 ---------------
194
194
195 The Perforce (P4) importer can be given a p4 depot path or a
195 The Perforce (P4) importer can be given a p4 depot path or a
196 client specification as source. It will convert all files in the
196 client specification as source. It will convert all files in the
197 source to a flat Mercurial repository, ignoring labels, branches
197 source to a flat Mercurial repository, ignoring labels, branches
198 and integrations. Note that when a depot path is given you then
198 and integrations. Note that when a depot path is given you then
199 usually should specify a target directory, because otherwise the
199 usually should specify a target directory, because otherwise the
200 target may be named ...-hg.
200 target may be named ...-hg.
201
201
202 It is possible to limit the amount of source history to be
202 It is possible to limit the amount of source history to be
203 converted by specifying an initial Perforce revision.
203 converted by specifying an initial Perforce revision.
204
204
205 --config convert.p4.startrev=0 (perforce changelist number)
205 --config convert.p4.startrev=0 (perforce changelist number)
206 specify initial Perforce revision.
206 specify initial Perforce revision.
207
207
208
208
209 Mercurial Destination
209 Mercurial Destination
210 ---------------------
210 ---------------------
211
211
212 --config convert.hg.clonebranches=False (boolean)
212 --config convert.hg.clonebranches=False (boolean)
213 dispatch source branches in separate clones.
213 dispatch source branches in separate clones.
214 --config convert.hg.tagsbranch=default (branch name)
214 --config convert.hg.tagsbranch=default (branch name)
215 tag revisions branch name
215 tag revisions branch name
216 --config convert.hg.usebranchnames=True (boolean)
216 --config convert.hg.usebranchnames=True (boolean)
217 preserve branch names
217 preserve branch names
218
218
219 """
219 """
220 return convcmd.convert(ui, src, dest, revmapfile, **opts)
220 return convcmd.convert(ui, src, dest, revmapfile, **opts)
221
221
222 def debugsvnlog(ui, **opts):
222 def debugsvnlog(ui, **opts):
223 return subversion.debugsvnlog(ui, **opts)
223 return subversion.debugsvnlog(ui, **opts)
224
224
225 def debugcvsps(ui, *args, **opts):
225 def debugcvsps(ui, *args, **opts):
226 '''create changeset information from CVS
226 '''create changeset information from CVS
227
227
228 This command is intended as a debugging tool for the CVS to
228 This command is intended as a debugging tool for the CVS to
229 Mercurial converter, and can be used as a direct replacement for
229 Mercurial converter, and can be used as a direct replacement for
230 cvsps.
230 cvsps.
231
231
232 Hg debugcvsps reads the CVS rlog for current directory (or any
232 Hg debugcvsps reads the CVS rlog for current directory (or any
233 named directory) in the CVS repository, and converts the log to a
233 named directory) in the CVS repository, and converts the log to a
234 series of changesets based on matching commit log entries and
234 series of changesets based on matching commit log entries and
235 dates.'''
235 dates.'''
236 return cvsps.debugcvsps(ui, *args, **opts)
236 return cvsps.debugcvsps(ui, *args, **opts)
237
237
238 commands.norepo += " convert debugsvnlog debugcvsps"
238 commands.norepo += " convert debugsvnlog debugcvsps"
239
239
240 cmdtable = {
240 cmdtable = {
241 "convert":
241 "convert":
242 (convert,
242 (convert,
243 [('A', 'authors', '', _('username mapping filename')),
243 [('A', 'authors', '', _('username mapping filename')),
244 ('d', 'dest-type', '', _('destination repository type')),
244 ('d', 'dest-type', '', _('destination repository type')),
245 ('', 'filemap', '', _('remap file names using contents of file')),
245 ('', 'filemap', '', _('remap file names using contents of file')),
246 ('r', 'rev', '', _('import up to target revision REV')),
246 ('r', 'rev', '', _('import up to target revision REV')),
247 ('s', 'source-type', '', _('source repository type')),
247 ('s', 'source-type', '', _('source repository type')),
248 ('', 'splicemap', '', _('splice synthesized history into place')),
248 ('', 'splicemap', '', _('splice synthesized history into place')),
249 ('', 'branchmap', '', _('change branch names while converting')),
249 ('', 'branchmap', '', _('change branch names while converting')),
250 ('', 'datesort', None, _('try to sort changesets by date'))],
250 ('', 'datesort', None, _('try to sort changesets by date'))],
251 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]')),
251 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]')),
252 "debugsvnlog":
252 "debugsvnlog":
253 (debugsvnlog,
253 (debugsvnlog,
254 [],
254 [],
255 'hg debugsvnlog'),
255 'hg debugsvnlog'),
256 "debugcvsps":
256 "debugcvsps":
257 (debugcvsps,
257 (debugcvsps,
258 [
258 [
259 # Main options shared with cvsps-2.1
259 # Main options shared with cvsps-2.1
260 ('b', 'branches', [], _('only return changes on specified branches')),
260 ('b', 'branches', [], _('only return changes on specified branches')),
261 ('p', 'prefix', '', _('prefix to remove from file names')),
261 ('p', 'prefix', '', _('prefix to remove from file names')),
262 ('r', 'revisions', [], _('only return changes after or between specified tags')),
262 ('r', 'revisions', [], _('only return changes after or between specified tags')),
263 ('u', 'update-cache', None, _("update cvs log cache")),
263 ('u', 'update-cache', None, _("update cvs log cache")),
264 ('x', 'new-cache', None, _("create new cvs log cache")),
264 ('x', 'new-cache', None, _("create new cvs log cache")),
265 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
265 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
266 ('', 'root', '', _('specify cvsroot')),
266 ('', 'root', '', _('specify cvsroot')),
267 # Options specific to builtin cvsps
267 # Options specific to builtin cvsps
268 ('', 'parents', '', _('show parent changesets')),
268 ('', 'parents', '', _('show parent changesets')),
269 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
269 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
270 # Options that are ignored for compatibility with cvsps-2.1
270 # Options that are ignored for compatibility with cvsps-2.1
271 ('A', 'cvs-direct', None, _('ignored for compatibility')),
271 ('A', 'cvs-direct', None, _('ignored for compatibility')),
272 ],
272 ],
273 _('hg debugcvsps [OPTION]... [PATH]...')),
273 _('hg debugcvsps [OPTION]... [PATH]...')),
274 }
274 }
@@ -1,342 +1,342 b''
1 # gnuarch.py - GNU Arch support for the convert extension
1 # gnuarch.py - GNU Arch support for the convert extension
2 #
2 #
3 # Copyright 2008, 2009 Aleix Conchillo Flaque <aleix@member.fsf.org>
3 # Copyright 2008, 2009 Aleix Conchillo Flaque <aleix@member.fsf.org>
4 # and others
4 # and others
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2, incorporated herein by reference.
7 # GNU General Public License version 2, incorporated herein by reference.
8
8
9 from common import NoRepo, commandline, commit, converter_source
9 from common import NoRepo, commandline, commit, converter_source
10 from mercurial.i18n import _
10 from mercurial.i18n import _
11 from mercurial import util
11 from mercurial import util
12 import os, shutil, tempfile, stat, locale
12 import os, shutil, tempfile, stat, locale
13 from email.Parser import Parser
13 from email.Parser import Parser
14
14
15 class gnuarch_source(converter_source, commandline):
15 class gnuarch_source(converter_source, commandline):
16
16
17 class gnuarch_rev:
17 class gnuarch_rev:
18 def __init__(self, rev):
18 def __init__(self, rev):
19 self.rev = rev
19 self.rev = rev
20 self.summary = ''
20 self.summary = ''
21 self.date = None
21 self.date = None
22 self.author = ''
22 self.author = ''
23 self.continuationof = None
23 self.continuationof = None
24 self.add_files = []
24 self.add_files = []
25 self.mod_files = []
25 self.mod_files = []
26 self.del_files = []
26 self.del_files = []
27 self.ren_files = {}
27 self.ren_files = {}
28 self.ren_dirs = {}
28 self.ren_dirs = {}
29
29
30 def __init__(self, ui, path, rev=None):
30 def __init__(self, ui, path, rev=None):
31 super(gnuarch_source, self).__init__(ui, path, rev=rev)
31 super(gnuarch_source, self).__init__(ui, path, rev=rev)
32
32
33 if not os.path.exists(os.path.join(path, '{arch}')):
33 if not os.path.exists(os.path.join(path, '{arch}')):
34 raise NoRepo(_("%s does not look like a GNU Arch repo") % path)
34 raise NoRepo(_("%s does not look like a GNU Arch repo") % path)
35
35
36 # Could use checktool, but we want to check for baz or tla.
36 # Could use checktool, but we want to check for baz or tla.
37 self.execmd = None
37 self.execmd = None
38 if util.find_exe('baz'):
38 if util.find_exe('baz'):
39 self.execmd = 'baz'
39 self.execmd = 'baz'
40 else:
40 else:
41 if util.find_exe('tla'):
41 if util.find_exe('tla'):
42 self.execmd = 'tla'
42 self.execmd = 'tla'
43 else:
43 else:
44 raise util.Abort(_('cannot find a GNU Arch tool'))
44 raise util.Abort(_('cannot find a GNU Arch tool'))
45
45
46 commandline.__init__(self, ui, self.execmd)
46 commandline.__init__(self, ui, self.execmd)
47
47
48 self.path = os.path.realpath(path)
48 self.path = os.path.realpath(path)
49 self.tmppath = None
49 self.tmppath = None
50
50
51 self.treeversion = None
51 self.treeversion = None
52 self.lastrev = None
52 self.lastrev = None
53 self.changes = {}
53 self.changes = {}
54 self.parents = {}
54 self.parents = {}
55 self.tags = {}
55 self.tags = {}
56 self.modecache = {}
56 self.modecache = {}
57 self.catlogparser = Parser()
57 self.catlogparser = Parser()
58 self.locale = locale.getpreferredencoding()
58 self.locale = locale.getpreferredencoding()
59 self.archives = []
59 self.archives = []
60
60
61 def before(self):
61 def before(self):
62 # Get registered archives
62 # Get registered archives
63 self.archives = [i.rstrip('\n')
63 self.archives = [i.rstrip('\n')
64 for i in self.runlines0('archives', '-n')]
64 for i in self.runlines0('archives', '-n')]
65
65
66 if self.execmd == 'tla':
66 if self.execmd == 'tla':
67 output = self.run0('tree-version', self.path)
67 output = self.run0('tree-version', self.path)
68 else:
68 else:
69 output = self.run0('tree-version', '-d', self.path)
69 output = self.run0('tree-version', '-d', self.path)
70 self.treeversion = output.strip()
70 self.treeversion = output.strip()
71
71
72 # Get name of temporary directory
72 # Get name of temporary directory
73 version = self.treeversion.split('/')
73 version = self.treeversion.split('/')
74 self.tmppath = os.path.join(tempfile.gettempdir(),
74 self.tmppath = os.path.join(tempfile.gettempdir(),
75 'hg-%s' % version[1])
75 'hg-%s' % version[1])
76
76
77 # Generate parents dictionary
77 # Generate parents dictionary
78 self.parents[None] = []
78 self.parents[None] = []
79 treeversion = self.treeversion
79 treeversion = self.treeversion
80 child = None
80 child = None
81 while treeversion:
81 while treeversion:
82 self.ui.status(_('analyzing tree version %s...\n') % treeversion)
82 self.ui.status(_('analyzing tree version %s...\n') % treeversion)
83
83
84 archive = treeversion.split('/')[0]
84 archive = treeversion.split('/')[0]
85 if archive not in self.archives:
85 if archive not in self.archives:
86 self.ui.status(_('tree analysis stopped because it points to '
86 self.ui.status(_('tree analysis stopped because it points to '
87 'an unregistered archive %s...\n') % archive)
87 'an unregistered archive %s...\n') % archive)
88 break
88 break
89
89
90 # Get the complete list of revisions for that tree version
90 # Get the complete list of revisions for that tree version
91 output, status = self.runlines('revisions', '-r', '-f', treeversion)
91 output, status = self.runlines('revisions', '-r', '-f', treeversion)
92 self.checkexit(status, 'failed retrieveing revisions for %s' % treeversion)
92 self.checkexit(status, 'failed retrieveing revisions for %s' % treeversion)
93
93
94 # No new iteration unless a revision has a continuation-of header
94 # No new iteration unless a revision has a continuation-of header
95 treeversion = None
95 treeversion = None
96
96
97 for l in output:
97 for l in output:
98 rev = l.strip()
98 rev = l.strip()
99 self.changes[rev] = self.gnuarch_rev(rev)
99 self.changes[rev] = self.gnuarch_rev(rev)
100 self.parents[rev] = []
100 self.parents[rev] = []
101
101
102 # Read author, date and summary
102 # Read author, date and summary
103 catlog, status = self.run('cat-log', '-d', self.path, rev)
103 catlog, status = self.run('cat-log', '-d', self.path, rev)
104 if status:
104 if status:
105 catlog = self.run0('cat-archive-log', rev)
105 catlog = self.run0('cat-archive-log', rev)
106 self._parsecatlog(catlog, rev)
106 self._parsecatlog(catlog, rev)
107
107
108 # Populate the parents map
108 # Populate the parents map
109 self.parents[child].append(rev)
109 self.parents[child].append(rev)
110
110
111 # Keep track of the current revision as the child of the next
111 # Keep track of the current revision as the child of the next
112 # revision scanned
112 # revision scanned
113 child = rev
113 child = rev
114
114
115 # Check if we have to follow the usual incremental history
115 # Check if we have to follow the usual incremental history
116 # or if we have to 'jump' to a different treeversion given
116 # or if we have to 'jump' to a different treeversion given
117 # by the continuation-of header.
117 # by the continuation-of header.
118 if self.changes[rev].continuationof:
118 if self.changes[rev].continuationof:
119 treeversion = '--'.join(self.changes[rev].continuationof.split('--')[:-1])
119 treeversion = '--'.join(self.changes[rev].continuationof.split('--')[:-1])
120 break
120 break
121
121
122 # If we reached a base-0 revision w/o any continuation-of
122 # If we reached a base-0 revision w/o any continuation-of
123 # header, it means the tree history ends here.
123 # header, it means the tree history ends here.
124 if rev[-6:] == 'base-0':
124 if rev[-6:] == 'base-0':
125 break
125 break
126
126
127 def after(self):
127 def after(self):
128 self.ui.debug(_('cleaning up %s\n') % self.tmppath)
128 self.ui.debug(_('cleaning up %s\n') % self.tmppath)
129 shutil.rmtree(self.tmppath, ignore_errors=True)
129 shutil.rmtree(self.tmppath, ignore_errors=True)
130
130
131 def getheads(self):
131 def getheads(self):
132 return self.parents[None]
132 return self.parents[None]
133
133
134 def getfile(self, name, rev):
134 def getfile(self, name, rev):
135 if rev != self.lastrev:
135 if rev != self.lastrev:
136 raise util.Abort(_('internal calling inconsistency'))
136 raise util.Abort(_('internal calling inconsistency'))
137
137
138 # Raise IOError if necessary (i.e. deleted files).
138 # Raise IOError if necessary (i.e. deleted files).
139 if not os.path.exists(os.path.join(self.tmppath, name)):
139 if not os.path.exists(os.path.join(self.tmppath, name)):
140 raise IOError
140 raise IOError
141
141
142 data, mode = self._getfile(name, rev)
142 data, mode = self._getfile(name, rev)
143 self.modecache[(name, rev)] = mode
143 self.modecache[(name, rev)] = mode
144
144
145 return data
145 return data
146
146
147 def getmode(self, name, rev):
147 def getmode(self, name, rev):
148 return self.modecache[(name, rev)]
148 return self.modecache[(name, rev)]
149
149
150 def getchanges(self, rev):
150 def getchanges(self, rev):
151 self.modecache = {}
151 self.modecache = {}
152 self._update(rev)
152 self._update(rev)
153 changes = []
153 changes = []
154 copies = {}
154 copies = {}
155
155
156 for f in self.changes[rev].add_files:
156 for f in self.changes[rev].add_files:
157 changes.append((f, rev))
157 changes.append((f, rev))
158
158
159 for f in self.changes[rev].mod_files:
159 for f in self.changes[rev].mod_files:
160 changes.append((f, rev))
160 changes.append((f, rev))
161
161
162 for f in self.changes[rev].del_files:
162 for f in self.changes[rev].del_files:
163 changes.append((f, rev))
163 changes.append((f, rev))
164
164
165 for src in self.changes[rev].ren_files:
165 for src in self.changes[rev].ren_files:
166 to = self.changes[rev].ren_files[src]
166 to = self.changes[rev].ren_files[src]
167 changes.append((src, rev))
167 changes.append((src, rev))
168 changes.append((to, rev))
168 changes.append((to, rev))
169 copies[to] = src
169 copies[to] = src
170
170
171 for src in self.changes[rev].ren_dirs:
171 for src in self.changes[rev].ren_dirs:
172 to = self.changes[rev].ren_dirs[src]
172 to = self.changes[rev].ren_dirs[src]
173 chgs, cps = self._rendirchanges(src, to);
173 chgs, cps = self._rendirchanges(src, to);
174 changes += [(f, rev) for f in chgs]
174 changes += [(f, rev) for f in chgs]
175 copies.update(cps)
175 copies.update(cps)
176
176
177 self.lastrev = rev
177 self.lastrev = rev
178 return sorted(set(changes)), copies
178 return sorted(set(changes)), copies
179
179
180 def getcommit(self, rev):
180 def getcommit(self, rev):
181 changes = self.changes[rev]
181 changes = self.changes[rev]
182 return commit(author=changes.author, date=changes.date,
182 return commit(author=changes.author, date=changes.date,
183 desc=changes.summary, parents=self.parents[rev], rev=rev)
183 desc=changes.summary, parents=self.parents[rev], rev=rev)
184
184
185 def gettags(self):
185 def gettags(self):
186 return self.tags
186 return self.tags
187
187
188 def _execute(self, cmd, *args, **kwargs):
188 def _execute(self, cmd, *args, **kwargs):
189 cmdline = [self.execmd, cmd]
189 cmdline = [self.execmd, cmd]
190 cmdline += args
190 cmdline += args
191 cmdline = [util.shellquote(arg) for arg in cmdline]
191 cmdline = [util.shellquote(arg) for arg in cmdline]
192 cmdline += ['>', util.nulldev, '2>', util.nulldev]
192 cmdline += ['>', util.nulldev, '2>', util.nulldev]
193 cmdline = util.quotecommand(' '.join(cmdline))
193 cmdline = util.quotecommand(' '.join(cmdline))
194 self.ui.debug(cmdline, '\n')
194 self.ui.debug(cmdline, '\n')
195 return os.system(cmdline)
195 return os.system(cmdline)
196
196
197 def _update(self, rev):
197 def _update(self, rev):
198 self.ui.debug(_('applying revision %s...\n') % rev)
198 self.ui.debug(_('applying revision %s...\n') % rev)
199 changeset, status = self.runlines('replay', '-d', self.tmppath,
199 changeset, status = self.runlines('replay', '-d', self.tmppath,
200 rev)
200 rev)
201 if status:
201 if status:
202 # Something went wrong while merging (baz or tla
202 # Something went wrong while merging (baz or tla
203 # issue?), get latest revision and try from there
203 # issue?), get latest revision and try from there
204 shutil.rmtree(self.tmppath, ignore_errors=True)
204 shutil.rmtree(self.tmppath, ignore_errors=True)
205 self._obtainrevision(rev)
205 self._obtainrevision(rev)
206 else:
206 else:
207 old_rev = self.parents[rev][0]
207 old_rev = self.parents[rev][0]
208 self.ui.debug(_('computing changeset between %s and %s...\n')
208 self.ui.debug(_('computing changeset between %s and %s...\n')
209 % (old_rev, rev))
209 % (old_rev, rev))
210 self._parsechangeset(changeset, rev)
210 self._parsechangeset(changeset, rev)
211
211
212 def _getfile(self, name, rev):
212 def _getfile(self, name, rev):
213 mode = os.lstat(os.path.join(self.tmppath, name)).st_mode
213 mode = os.lstat(os.path.join(self.tmppath, name)).st_mode
214 if stat.S_ISLNK(mode):
214 if stat.S_ISLNK(mode):
215 data = os.readlink(os.path.join(self.tmppath, name))
215 data = os.readlink(os.path.join(self.tmppath, name))
216 mode = mode and 'l' or ''
216 mode = mode and 'l' or ''
217 else:
217 else:
218 data = open(os.path.join(self.tmppath, name), 'rb').read()
218 data = open(os.path.join(self.tmppath, name), 'rb').read()
219 mode = (mode & 0111) and 'x' or ''
219 mode = (mode & 0111) and 'x' or ''
220 return data, mode
220 return data, mode
221
221
222 def _exclude(self, name):
222 def _exclude(self, name):
223 exclude = [ '{arch}', '.arch-ids', '.arch-inventory' ]
223 exclude = [ '{arch}', '.arch-ids', '.arch-inventory' ]
224 for exc in exclude:
224 for exc in exclude:
225 if name.find(exc) != -1:
225 if name.find(exc) != -1:
226 return True
226 return True
227 return False
227 return False
228
228
229 def _readcontents(self, path):
229 def _readcontents(self, path):
230 files = []
230 files = []
231 contents = os.listdir(path)
231 contents = os.listdir(path)
232 while len(contents) > 0:
232 while len(contents) > 0:
233 c = contents.pop()
233 c = contents.pop()
234 p = os.path.join(path, c)
234 p = os.path.join(path, c)
235 # os.walk could be used, but here we avoid internal GNU
235 # os.walk could be used, but here we avoid internal GNU
236 # Arch files and directories, thus saving a lot time.
236 # Arch files and directories, thus saving a lot time.
237 if not self._exclude(p):
237 if not self._exclude(p):
238 if os.path.isdir(p):
238 if os.path.isdir(p):
239 contents += [os.path.join(c, f) for f in os.listdir(p)]
239 contents += [os.path.join(c, f) for f in os.listdir(p)]
240 else:
240 else:
241 files.append(c)
241 files.append(c)
242 return files
242 return files
243
243
244 def _rendirchanges(self, src, dest):
244 def _rendirchanges(self, src, dest):
245 changes = []
245 changes = []
246 copies = {}
246 copies = {}
247 files = self._readcontents(os.path.join(self.tmppath, dest))
247 files = self._readcontents(os.path.join(self.tmppath, dest))
248 for f in files:
248 for f in files:
249 s = os.path.join(src, f)
249 s = os.path.join(src, f)
250 d = os.path.join(dest, f)
250 d = os.path.join(dest, f)
251 changes.append(s)
251 changes.append(s)
252 changes.append(d)
252 changes.append(d)
253 copies[d] = s
253 copies[d] = s
254 return changes, copies
254 return changes, copies
255
255
256 def _obtainrevision(self, rev):
256 def _obtainrevision(self, rev):
257 self.ui.debug(_('obtaining revision %s...\n') % rev)
257 self.ui.debug(_('obtaining revision %s...\n') % rev)
258 output = self._execute('get', rev, self.tmppath)
258 output = self._execute('get', rev, self.tmppath)
259 self.checkexit(output)
259 self.checkexit(output)
260 self.ui.debug(_('analysing revision %s...\n') % rev)
260 self.ui.debug(_('analyzing revision %s...\n') % rev)
261 files = self._readcontents(self.tmppath)
261 files = self._readcontents(self.tmppath)
262 self.changes[rev].add_files += files
262 self.changes[rev].add_files += files
263
263
264 def _stripbasepath(self, path):
264 def _stripbasepath(self, path):
265 if path.startswith('./'):
265 if path.startswith('./'):
266 return path[2:]
266 return path[2:]
267 return path
267 return path
268
268
269 def _parsecatlog(self, data, rev):
269 def _parsecatlog(self, data, rev):
270 try:
270 try:
271 catlog = self.catlogparser.parsestr(data)
271 catlog = self.catlogparser.parsestr(data)
272
272
273 # Commit date
273 # Commit date
274 self.changes[rev].date = util.datestr(
274 self.changes[rev].date = util.datestr(
275 util.strdate(catlog['Standard-date'],
275 util.strdate(catlog['Standard-date'],
276 '%Y-%m-%d %H:%M:%S'))
276 '%Y-%m-%d %H:%M:%S'))
277
277
278 # Commit author
278 # Commit author
279 self.changes[rev].author = self.recode(catlog['Creator'])
279 self.changes[rev].author = self.recode(catlog['Creator'])
280
280
281 # Commit description
281 # Commit description
282 self.changes[rev].summary = '\n\n'.join((catlog['Summary'],
282 self.changes[rev].summary = '\n\n'.join((catlog['Summary'],
283 catlog.get_payload()))
283 catlog.get_payload()))
284 self.changes[rev].summary = self.recode(self.changes[rev].summary)
284 self.changes[rev].summary = self.recode(self.changes[rev].summary)
285
285
286 # Commit revision origin when dealing with a branch or tag
286 # Commit revision origin when dealing with a branch or tag
287 if catlog.has_key('Continuation-of'):
287 if catlog.has_key('Continuation-of'):
288 self.changes[rev].continuationof = self.recode(catlog['Continuation-of'])
288 self.changes[rev].continuationof = self.recode(catlog['Continuation-of'])
289 except Exception:
289 except Exception:
290 raise util.Abort(_('could not parse cat-log of %s') % rev)
290 raise util.Abort(_('could not parse cat-log of %s') % rev)
291
291
292 def _parsechangeset(self, data, rev):
292 def _parsechangeset(self, data, rev):
293 for l in data:
293 for l in data:
294 l = l.strip()
294 l = l.strip()
295 # Added file (ignore added directory)
295 # Added file (ignore added directory)
296 if l.startswith('A') and not l.startswith('A/'):
296 if l.startswith('A') and not l.startswith('A/'):
297 file = self._stripbasepath(l[1:].strip())
297 file = self._stripbasepath(l[1:].strip())
298 if not self._exclude(file):
298 if not self._exclude(file):
299 self.changes[rev].add_files.append(file)
299 self.changes[rev].add_files.append(file)
300 # Deleted file (ignore deleted directory)
300 # Deleted file (ignore deleted directory)
301 elif l.startswith('D') and not l.startswith('D/'):
301 elif l.startswith('D') and not l.startswith('D/'):
302 file = self._stripbasepath(l[1:].strip())
302 file = self._stripbasepath(l[1:].strip())
303 if not self._exclude(file):
303 if not self._exclude(file):
304 self.changes[rev].del_files.append(file)
304 self.changes[rev].del_files.append(file)
305 # Modified binary file
305 # Modified binary file
306 elif l.startswith('Mb'):
306 elif l.startswith('Mb'):
307 file = self._stripbasepath(l[2:].strip())
307 file = self._stripbasepath(l[2:].strip())
308 if not self._exclude(file):
308 if not self._exclude(file):
309 self.changes[rev].mod_files.append(file)
309 self.changes[rev].mod_files.append(file)
310 # Modified link
310 # Modified link
311 elif l.startswith('M->'):
311 elif l.startswith('M->'):
312 file = self._stripbasepath(l[3:].strip())
312 file = self._stripbasepath(l[3:].strip())
313 if not self._exclude(file):
313 if not self._exclude(file):
314 self.changes[rev].mod_files.append(file)
314 self.changes[rev].mod_files.append(file)
315 # Modified file
315 # Modified file
316 elif l.startswith('M'):
316 elif l.startswith('M'):
317 file = self._stripbasepath(l[1:].strip())
317 file = self._stripbasepath(l[1:].strip())
318 if not self._exclude(file):
318 if not self._exclude(file):
319 self.changes[rev].mod_files.append(file)
319 self.changes[rev].mod_files.append(file)
320 # Renamed file (or link)
320 # Renamed file (or link)
321 elif l.startswith('=>'):
321 elif l.startswith('=>'):
322 files = l[2:].strip().split(' ')
322 files = l[2:].strip().split(' ')
323 if len(files) == 1:
323 if len(files) == 1:
324 files = l[2:].strip().split('\t')
324 files = l[2:].strip().split('\t')
325 src = self._stripbasepath(files[0])
325 src = self._stripbasepath(files[0])
326 dst = self._stripbasepath(files[1])
326 dst = self._stripbasepath(files[1])
327 if not self._exclude(src) and not self._exclude(dst):
327 if not self._exclude(src) and not self._exclude(dst):
328 self.changes[rev].ren_files[src] = dst
328 self.changes[rev].ren_files[src] = dst
329 # Conversion from file to link or from link to file (modified)
329 # Conversion from file to link or from link to file (modified)
330 elif l.startswith('ch'):
330 elif l.startswith('ch'):
331 file = self._stripbasepath(l[2:].strip())
331 file = self._stripbasepath(l[2:].strip())
332 if not self._exclude(file):
332 if not self._exclude(file):
333 self.changes[rev].mod_files.append(file)
333 self.changes[rev].mod_files.append(file)
334 # Renamed directory
334 # Renamed directory
335 elif l.startswith('/>'):
335 elif l.startswith('/>'):
336 dirs = l[2:].strip().split(' ')
336 dirs = l[2:].strip().split(' ')
337 if len(dirs) == 1:
337 if len(dirs) == 1:
338 dirs = l[2:].strip().split('\t')
338 dirs = l[2:].strip().split('\t')
339 src = self._stripbasepath(dirs[0])
339 src = self._stripbasepath(dirs[0])
340 dst = self._stripbasepath(dirs[1])
340 dst = self._stripbasepath(dirs[1])
341 if not self._exclude(src) and not self._exclude(dst):
341 if not self._exclude(src) and not self._exclude(dst):
342 self.changes[rev].ren_dirs[src] = dst
342 self.changes[rev].ren_dirs[src] = dst
@@ -1,233 +1,233 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 '''
8 '''
9 The `extdiff' Mercurial extension allows you to use external programs
9 The `extdiff' Mercurial extension allows you to use external programs
10 to compare revisions, or revision with working directory. The external diff
10 to compare revisions, or revision with working directory. The external diff
11 programs are called with a configurable set of options and two
11 programs are called with a configurable set of options and two
12 non-option arguments: paths to directories containing snapshots of
12 non-option arguments: paths to directories containing snapshots of
13 files to compare.
13 files to compare.
14
14
15 To enable this extension:
15 To enable this extension:
16
16
17 [extensions]
17 [extensions]
18 hgext.extdiff =
18 hgext.extdiff =
19
19
20 The `extdiff' extension also allows to configure new diff commands, so
20 The `extdiff' extension also allows to configure new diff commands, so
21 you do not need to type "hg extdiff -p kdiff3" always.
21 you do not need to type "hg extdiff -p kdiff3" always.
22
22
23 [extdiff]
23 [extdiff]
24 # add new command that runs GNU diff(1) in 'context diff' mode
24 # add new command that runs GNU diff(1) in 'context diff' mode
25 cdiff = gdiff -Nprc5
25 cdiff = gdiff -Nprc5
26 ## or the old way:
26 ## or the old way:
27 #cmd.cdiff = gdiff
27 #cmd.cdiff = gdiff
28 #opts.cdiff = -Nprc5
28 #opts.cdiff = -Nprc5
29
29
30 # add new command called vdiff, runs kdiff3
30 # add new command called vdiff, runs kdiff3
31 vdiff = kdiff3
31 vdiff = kdiff3
32
32
33 # add new command called meld, runs meld (no need to name twice)
33 # add new command called meld, runs meld (no need to name twice)
34 meld =
34 meld =
35
35
36 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
36 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
37 # (see http://www.vim.org/scripts/script.php?script_id=102)
37 # (see http://www.vim.org/scripts/script.php?script_id=102)
38 # Non english user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
38 # Non English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
39 # your .vimrc
39 # your .vimrc
40 vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)'
40 vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)'
41
41
42 You can use -I/-X and list of file or directory names like normal "hg
42 You can use -I/-X and list of file or directory names like normal "hg
43 diff" command. The `extdiff' extension makes snapshots of only needed
43 diff" command. The `extdiff' extension makes snapshots of only needed
44 files, so running the external diff program will actually be pretty
44 files, so running the external diff program will actually be pretty
45 fast (at least faster than having to compare the entire tree).
45 fast (at least faster than having to compare the entire tree).
46 '''
46 '''
47
47
48 from mercurial.i18n import _
48 from mercurial.i18n import _
49 from mercurial.node import short
49 from mercurial.node import short
50 from mercurial import cmdutil, util, commands
50 from mercurial import cmdutil, util, commands
51 import os, shlex, shutil, tempfile
51 import os, shlex, shutil, tempfile
52
52
53 def snapshot(ui, repo, files, node, tmproot):
53 def snapshot(ui, repo, files, node, tmproot):
54 '''snapshot files as of some revision
54 '''snapshot files as of some revision
55 if not using snapshot, -I/-X does not work and recursive diff
55 if not using snapshot, -I/-X does not work and recursive diff
56 in tools like kdiff3 and meld displays too many files.'''
56 in tools like kdiff3 and meld displays too many files.'''
57 dirname = os.path.basename(repo.root)
57 dirname = os.path.basename(repo.root)
58 if dirname == "":
58 if dirname == "":
59 dirname = "root"
59 dirname = "root"
60 if node is not None:
60 if node is not None:
61 dirname = '%s.%s' % (dirname, short(node))
61 dirname = '%s.%s' % (dirname, short(node))
62 base = os.path.join(tmproot, dirname)
62 base = os.path.join(tmproot, dirname)
63 os.mkdir(base)
63 os.mkdir(base)
64 if node is not None:
64 if node is not None:
65 ui.note(_('making snapshot of %d files from rev %s\n') %
65 ui.note(_('making snapshot of %d files from rev %s\n') %
66 (len(files), short(node)))
66 (len(files), short(node)))
67 else:
67 else:
68 ui.note(_('making snapshot of %d files from working directory\n') %
68 ui.note(_('making snapshot of %d files from working directory\n') %
69 (len(files)))
69 (len(files)))
70 wopener = util.opener(base)
70 wopener = util.opener(base)
71 fns_and_mtime = []
71 fns_and_mtime = []
72 ctx = repo[node]
72 ctx = repo[node]
73 for fn in files:
73 for fn in files:
74 wfn = util.pconvert(fn)
74 wfn = util.pconvert(fn)
75 if not wfn in ctx:
75 if not wfn in ctx:
76 # skipping new file after a merge ?
76 # skipping new file after a merge ?
77 continue
77 continue
78 ui.note(' %s\n' % wfn)
78 ui.note(' %s\n' % wfn)
79 dest = os.path.join(base, wfn)
79 dest = os.path.join(base, wfn)
80 fctx = ctx[wfn]
80 fctx = ctx[wfn]
81 data = repo.wwritedata(wfn, fctx.data())
81 data = repo.wwritedata(wfn, fctx.data())
82 if 'l' in fctx.flags():
82 if 'l' in fctx.flags():
83 wopener.symlink(data, wfn)
83 wopener.symlink(data, wfn)
84 else:
84 else:
85 wopener(wfn, 'w').write(data)
85 wopener(wfn, 'w').write(data)
86 if 'x' in fctx.flags():
86 if 'x' in fctx.flags():
87 util.set_flags(dest, False, True)
87 util.set_flags(dest, False, True)
88 if node is None:
88 if node is None:
89 fns_and_mtime.append((dest, repo.wjoin(fn), os.path.getmtime(dest)))
89 fns_and_mtime.append((dest, repo.wjoin(fn), os.path.getmtime(dest)))
90 return dirname, fns_and_mtime
90 return dirname, fns_and_mtime
91
91
92 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
92 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
93 '''Do the actuall diff:
93 '''Do the actuall diff:
94
94
95 - copy to a temp structure if diffing 2 internal revisions
95 - copy to a temp structure if diffing 2 internal revisions
96 - copy to a temp structure if diffing working revision with
96 - copy to a temp structure if diffing working revision with
97 another one and more than 1 file is changed
97 another one and more than 1 file is changed
98 - just invoke the diff for a single file in the working dir
98 - just invoke the diff for a single file in the working dir
99 '''
99 '''
100
100
101 revs = opts.get('rev')
101 revs = opts.get('rev')
102 change = opts.get('change')
102 change = opts.get('change')
103
103
104 if revs and change:
104 if revs and change:
105 msg = _('cannot specify --rev and --change at the same time')
105 msg = _('cannot specify --rev and --change at the same time')
106 raise util.Abort(msg)
106 raise util.Abort(msg)
107 elif change:
107 elif change:
108 node2 = repo.lookup(change)
108 node2 = repo.lookup(change)
109 node1 = repo[node2].parents()[0].node()
109 node1 = repo[node2].parents()[0].node()
110 else:
110 else:
111 node1, node2 = cmdutil.revpair(repo, revs)
111 node1, node2 = cmdutil.revpair(repo, revs)
112
112
113 matcher = cmdutil.match(repo, pats, opts)
113 matcher = cmdutil.match(repo, pats, opts)
114 modified, added, removed = repo.status(node1, node2, matcher)[:3]
114 modified, added, removed = repo.status(node1, node2, matcher)[:3]
115 if not (modified or added or removed):
115 if not (modified or added or removed):
116 return 0
116 return 0
117
117
118 tmproot = tempfile.mkdtemp(prefix='extdiff.')
118 tmproot = tempfile.mkdtemp(prefix='extdiff.')
119 dir2root = ''
119 dir2root = ''
120 try:
120 try:
121 # Always make a copy of node1
121 # Always make a copy of node1
122 dir1 = snapshot(ui, repo, modified + removed, node1, tmproot)[0]
122 dir1 = snapshot(ui, repo, modified + removed, node1, tmproot)[0]
123 changes = len(modified) + len(removed) + len(added)
123 changes = len(modified) + len(removed) + len(added)
124
124
125 # If node2 in not the wc or there is >1 change, copy it
125 # If node2 in not the wc or there is >1 change, copy it
126 if node2 or changes > 1:
126 if node2 or changes > 1:
127 dir2, fns_and_mtime = snapshot(ui, repo, modified + added, node2, tmproot)
127 dir2, fns_and_mtime = snapshot(ui, repo, modified + added, node2, tmproot)
128 else:
128 else:
129 # This lets the diff tool open the changed file directly
129 # This lets the diff tool open the changed file directly
130 dir2 = ''
130 dir2 = ''
131 dir2root = repo.root
131 dir2root = repo.root
132 fns_and_mtime = []
132 fns_and_mtime = []
133
133
134 # If only one change, diff the files instead of the directories
134 # If only one change, diff the files instead of the directories
135 if changes == 1 :
135 if changes == 1 :
136 if len(modified):
136 if len(modified):
137 dir1 = os.path.join(dir1, util.localpath(modified[0]))
137 dir1 = os.path.join(dir1, util.localpath(modified[0]))
138 dir2 = os.path.join(dir2root, dir2, util.localpath(modified[0]))
138 dir2 = os.path.join(dir2root, dir2, util.localpath(modified[0]))
139 elif len(removed) :
139 elif len(removed) :
140 dir1 = os.path.join(dir1, util.localpath(removed[0]))
140 dir1 = os.path.join(dir1, util.localpath(removed[0]))
141 dir2 = os.devnull
141 dir2 = os.devnull
142 else:
142 else:
143 dir1 = os.devnull
143 dir1 = os.devnull
144 dir2 = os.path.join(dir2root, dir2, util.localpath(added[0]))
144 dir2 = os.path.join(dir2root, dir2, util.localpath(added[0]))
145
145
146 cmdline = ('%s %s %s %s' %
146 cmdline = ('%s %s %s %s' %
147 (util.shellquote(diffcmd), ' '.join(diffopts),
147 (util.shellquote(diffcmd), ' '.join(diffopts),
148 util.shellquote(dir1), util.shellquote(dir2)))
148 util.shellquote(dir1), util.shellquote(dir2)))
149 ui.debug(_('running %r in %s\n') % (cmdline, tmproot))
149 ui.debug(_('running %r in %s\n') % (cmdline, tmproot))
150 util.system(cmdline, cwd=tmproot)
150 util.system(cmdline, cwd=tmproot)
151
151
152 for copy_fn, working_fn, mtime in fns_and_mtime:
152 for copy_fn, working_fn, mtime in fns_and_mtime:
153 if os.path.getmtime(copy_fn) != mtime:
153 if os.path.getmtime(copy_fn) != mtime:
154 ui.debug(_('file changed while diffing. '
154 ui.debug(_('file changed while diffing. '
155 'Overwriting: %s (src: %s)\n') % (working_fn, copy_fn))
155 'Overwriting: %s (src: %s)\n') % (working_fn, copy_fn))
156 util.copyfile(copy_fn, working_fn)
156 util.copyfile(copy_fn, working_fn)
157
157
158 return 1
158 return 1
159 finally:
159 finally:
160 ui.note(_('cleaning up temp directory\n'))
160 ui.note(_('cleaning up temp directory\n'))
161 shutil.rmtree(tmproot)
161 shutil.rmtree(tmproot)
162
162
163 def extdiff(ui, repo, *pats, **opts):
163 def extdiff(ui, repo, *pats, **opts):
164 '''use external program to diff repository (or selected files)
164 '''use external program to diff repository (or selected files)
165
165
166 Show differences between revisions for the specified files, using
166 Show differences between revisions for the specified files, using
167 an external program. The default program used is diff, with
167 an external program. The default program used is diff, with
168 default options "-Npru".
168 default options "-Npru".
169
169
170 To select a different program, use the -p/--program option. The
170 To select a different program, use the -p/--program option. The
171 program will be passed the names of two directories to compare. To
171 program will be passed the names of two directories to compare. To
172 pass additional options to the program, use -o/--option. These
172 pass additional options to the program, use -o/--option. These
173 will be passed before the names of the directories to compare.
173 will be passed before the names of the directories to compare.
174
174
175 When two revision arguments are given, then changes are shown
175 When two revision arguments are given, then changes are shown
176 between those revisions. If only one revision is specified then
176 between those revisions. If only one revision is specified then
177 that revision is compared to the working directory, and, when no
177 that revision is compared to the working directory, and, when no
178 revisions are specified, the working directory files are compared
178 revisions are specified, the working directory files are compared
179 to its parent.'''
179 to its parent.'''
180 program = opts['program'] or 'diff'
180 program = opts['program'] or 'diff'
181 if opts['program']:
181 if opts['program']:
182 option = opts['option']
182 option = opts['option']
183 else:
183 else:
184 option = opts['option'] or ['-Npru']
184 option = opts['option'] or ['-Npru']
185 return dodiff(ui, repo, program, option, pats, opts)
185 return dodiff(ui, repo, program, option, pats, opts)
186
186
187 cmdtable = {
187 cmdtable = {
188 "extdiff":
188 "extdiff":
189 (extdiff,
189 (extdiff,
190 [('p', 'program', '', _('comparison program to run')),
190 [('p', 'program', '', _('comparison program to run')),
191 ('o', 'option', [], _('pass option to comparison program')),
191 ('o', 'option', [], _('pass option to comparison program')),
192 ('r', 'rev', [], _('revision')),
192 ('r', 'rev', [], _('revision')),
193 ('c', 'change', '', _('change made by revision')),
193 ('c', 'change', '', _('change made by revision')),
194 ] + commands.walkopts,
194 ] + commands.walkopts,
195 _('hg extdiff [OPT]... [FILE]...')),
195 _('hg extdiff [OPT]... [FILE]...')),
196 }
196 }
197
197
198 def uisetup(ui):
198 def uisetup(ui):
199 for cmd, path in ui.configitems('extdiff'):
199 for cmd, path in ui.configitems('extdiff'):
200 if cmd.startswith('cmd.'):
200 if cmd.startswith('cmd.'):
201 cmd = cmd[4:]
201 cmd = cmd[4:]
202 if not path: path = cmd
202 if not path: path = cmd
203 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
203 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
204 diffopts = diffopts and [diffopts] or []
204 diffopts = diffopts and [diffopts] or []
205 elif cmd.startswith('opts.'):
205 elif cmd.startswith('opts.'):
206 continue
206 continue
207 else:
207 else:
208 # command = path opts
208 # command = path opts
209 if path:
209 if path:
210 diffopts = shlex.split(path)
210 diffopts = shlex.split(path)
211 path = diffopts.pop(0)
211 path = diffopts.pop(0)
212 else:
212 else:
213 path, diffopts = cmd, []
213 path, diffopts = cmd, []
214 def save(cmd, path, diffopts):
214 def save(cmd, path, diffopts):
215 '''use closure to save diff command to use'''
215 '''use closure to save diff command to use'''
216 def mydiff(ui, repo, *pats, **opts):
216 def mydiff(ui, repo, *pats, **opts):
217 return dodiff(ui, repo, path, diffopts, pats, opts)
217 return dodiff(ui, repo, path, diffopts, pats, opts)
218 mydiff.__doc__ = '''use %(path)s to diff repository (or selected files)
218 mydiff.__doc__ = '''use %(path)s to diff repository (or selected files)
219
219
220 Show differences between revisions for the specified
220 Show differences between revisions for the specified
221 files, using the %(path)s program.
221 files, using the %(path)s program.
222
222
223 When two revision arguments are given, then changes are
223 When two revision arguments are given, then changes are
224 shown between those revisions. If only one revision is
224 shown between those revisions. If only one revision is
225 specified then that revision is compared to the working
225 specified then that revision is compared to the working
226 directory, and, when no revisions are specified, the
226 directory, and, when no revisions are specified, the
227 working directory files are compared to its parent.''' % {
227 working directory files are compared to its parent.''' % {
228 'path': util.uirepr(path),
228 'path': util.uirepr(path),
229 }
229 }
230 return mydiff
230 return mydiff
231 cmdtable[cmd] = (save(cmd, path, diffopts),
231 cmdtable[cmd] = (save(cmd, path, diffopts),
232 cmdtable['extdiff'][1][1:],
232 cmdtable['extdiff'][1][1:],
233 _('hg %s [OPTION]... [FILE]...') % cmd)
233 _('hg %s [OPTION]... [FILE]...') % cmd)
@@ -1,534 +1,534 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 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://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$ in
37 This extension expands RCS/CVS-like or self-customized $Keywords$ in
38 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 the
40 Keywords are only expanded in local repositories and not stored in the
41 change history. The mechanism can be regarded as a convenience for the
41 change history. The mechanism can be regarded as a convenience for the
42 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 of
44 Configuration is done in the [keyword] and [keywordmaps] sections of
45 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 repositories.
55 the less you lose speed in huge repositories.
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
62 The default template mappings (view with "hg kwdemo -d") can be
63 replaced with customized keywords and templates. Again, run "hg
63 replaced with customized keywords and templates. Again, run "hg
64 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
67 the risk of inadvertently storing expanded keywords in the change
68 history.
68 history.
69
69
70 To force expansion after enabling it, or a configuration change, run
70 To force expansion after enabling it, or a configuration change, run
71 "hg kwexpand".
71 "hg kwexpand".
72
72
73 Also, when committing with the record extension or using mq's qrecord,
73 Also, when committing with the record extension or using mq's qrecord,
74 be aware that keywords cannot be updated. Again, run "hg kwexpand" on
74 be aware that keywords cannot be updated. Again, run "hg kwexpand" on
75 the files in question to update keyword expansions after all changes
75 the files in question to update keyword expansions after all changes
76 have been checked in.
76 have been checked in.
77
77
78 Expansions spanning more than one line and incremental expansions,
78 Expansions spanning more than one line and incremental expansions,
79 like CVS' $Log$, are not supported. A keyword template map
79 like CVS' $Log$, are not supported. A keyword template map
80 "Log = {desc}" expands to the first line of the changeset description.
80 "Log = {desc}" expands to the first line of the changeset description.
81 '''
81 '''
82
82
83 from mercurial import commands, cmdutil, dispatch, filelog, revlog, extensions
83 from mercurial import commands, cmdutil, dispatch, filelog, revlog, extensions
84 from mercurial import patch, localrepo, templater, templatefilters, util, match
84 from mercurial import patch, localrepo, templater, templatefilters, util, match
85 from mercurial.hgweb import webcommands
85 from mercurial.hgweb import webcommands
86 from mercurial.lock import release
86 from mercurial.lock import release
87 from mercurial.node import nullid, hex
87 from mercurial.node import nullid, hex
88 from mercurial.i18n import _
88 from mercurial.i18n import _
89 import re, shutil, tempfile, time
89 import re, shutil, tempfile, time
90
90
91 commands.optionalrepo += ' kwdemo'
91 commands.optionalrepo += ' kwdemo'
92
92
93 # hg commands that do not act on keywords
93 # hg commands that do not act on keywords
94 nokwcommands = ('add addremove annotate bundle copy export grep incoming init'
94 nokwcommands = ('add addremove annotate bundle copy export grep incoming init'
95 ' log outgoing push rename rollback tip verify'
95 ' log outgoing push rename rollback tip verify'
96 ' convert email glog')
96 ' convert email glog')
97
97
98 # hg commands that trigger expansion only when writing to working dir,
98 # hg commands that trigger expansion only when writing to working dir,
99 # not when reading filelog, and unexpand when reading from working dir
99 # not when reading filelog, and unexpand when reading from working dir
100 restricted = 'merge record resolve qfold qimport qnew qpush qrefresh qrecord'
100 restricted = 'merge record resolve qfold qimport qnew qpush qrefresh qrecord'
101
101
102 def utcdate(date):
102 def utcdate(date):
103 '''Returns hgdate in cvs-like UTC format.'''
103 '''Returns hgdate in cvs-like UTC format.'''
104 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
104 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
105
105
106 # make keyword tools accessible
106 # make keyword tools accessible
107 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
107 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
108
108
109
109
110 class kwtemplater(object):
110 class kwtemplater(object):
111 '''
111 '''
112 Sets up keyword templates, corresponding keyword regex, and
112 Sets up keyword templates, corresponding keyword regex, and
113 provides keyword substitution functions.
113 provides keyword substitution functions.
114 '''
114 '''
115 templates = {
115 templates = {
116 'Revision': '{node|short}',
116 'Revision': '{node|short}',
117 'Author': '{author|user}',
117 'Author': '{author|user}',
118 'Date': '{date|utcdate}',
118 'Date': '{date|utcdate}',
119 'RCSFile': '{file|basename},v',
119 'RCSFile': '{file|basename},v',
120 'Source': '{root}/{file},v',
120 'Source': '{root}/{file},v',
121 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
121 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
122 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
122 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
123 }
123 }
124
124
125 def __init__(self, ui, repo):
125 def __init__(self, ui, repo):
126 self.ui = ui
126 self.ui = ui
127 self.repo = repo
127 self.repo = repo
128 self.match = match.match(repo.root, '', [],
128 self.match = match.match(repo.root, '', [],
129 kwtools['inc'], kwtools['exc'])
129 kwtools['inc'], kwtools['exc'])
130 self.restrict = kwtools['hgcmd'] in restricted.split()
130 self.restrict = kwtools['hgcmd'] in restricted.split()
131
131
132 kwmaps = self.ui.configitems('keywordmaps')
132 kwmaps = self.ui.configitems('keywordmaps')
133 if kwmaps: # override default templates
133 if kwmaps: # override default templates
134 kwmaps = [(k, templater.parsestring(v, False))
134 kwmaps = [(k, templater.parsestring(v, False))
135 for (k, v) in kwmaps]
135 for (k, v) in kwmaps]
136 self.templates = dict(kwmaps)
136 self.templates = dict(kwmaps)
137 escaped = map(re.escape, self.templates.keys())
137 escaped = map(re.escape, self.templates.keys())
138 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
138 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
139 self.re_kw = re.compile(kwpat)
139 self.re_kw = re.compile(kwpat)
140
140
141 templatefilters.filters['utcdate'] = utcdate
141 templatefilters.filters['utcdate'] = utcdate
142 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
142 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
143 False, None, '', False)
143 False, None, '', False)
144
144
145 def substitute(self, data, path, ctx, subfunc):
145 def substitute(self, data, path, ctx, subfunc):
146 '''Replaces keywords in data with expanded template.'''
146 '''Replaces keywords in data with expanded template.'''
147 def kwsub(mobj):
147 def kwsub(mobj):
148 kw = mobj.group(1)
148 kw = mobj.group(1)
149 self.ct.use_template(self.templates[kw])
149 self.ct.use_template(self.templates[kw])
150 self.ui.pushbuffer()
150 self.ui.pushbuffer()
151 self.ct.show(ctx, root=self.repo.root, file=path)
151 self.ct.show(ctx, root=self.repo.root, file=path)
152 ekw = templatefilters.firstline(self.ui.popbuffer())
152 ekw = templatefilters.firstline(self.ui.popbuffer())
153 return '$%s: %s $' % (kw, ekw)
153 return '$%s: %s $' % (kw, ekw)
154 return subfunc(kwsub, data)
154 return subfunc(kwsub, data)
155
155
156 def expand(self, path, node, data):
156 def expand(self, path, node, data):
157 '''Returns data with keywords expanded.'''
157 '''Returns data with keywords expanded.'''
158 if not self.restrict and self.match(path) and not util.binary(data):
158 if not self.restrict and self.match(path) and not util.binary(data):
159 ctx = self.repo.filectx(path, fileid=node).changectx()
159 ctx = self.repo.filectx(path, fileid=node).changectx()
160 return self.substitute(data, path, ctx, self.re_kw.sub)
160 return self.substitute(data, path, ctx, self.re_kw.sub)
161 return data
161 return data
162
162
163 def iskwfile(self, path, flagfunc):
163 def iskwfile(self, path, flagfunc):
164 '''Returns true if path matches [keyword] pattern
164 '''Returns true if path matches [keyword] pattern
165 and is not a symbolic link.
165 and is not a symbolic link.
166 Caveat: localrepository._link fails on Windows.'''
166 Caveat: localrepository._link fails on Windows.'''
167 return self.match(path) and not 'l' in flagfunc(path)
167 return self.match(path) and not 'l' in flagfunc(path)
168
168
169 def overwrite(self, node, expand, files):
169 def overwrite(self, node, expand, files):
170 '''Overwrites selected files expanding/shrinking keywords.'''
170 '''Overwrites selected files expanding/shrinking keywords.'''
171 ctx = self.repo[node]
171 ctx = self.repo[node]
172 mf = ctx.manifest()
172 mf = ctx.manifest()
173 if node is not None: # commit
173 if node is not None: # commit
174 files = [f for f in ctx.files() if f in mf]
174 files = [f for f in ctx.files() if f in mf]
175 notify = self.ui.debug
175 notify = self.ui.debug
176 else: # kwexpand/kwshrink
176 else: # kwexpand/kwshrink
177 notify = self.ui.note
177 notify = self.ui.note
178 candidates = [f for f in files if self.iskwfile(f, ctx.flags)]
178 candidates = [f for f in files if self.iskwfile(f, ctx.flags)]
179 if candidates:
179 if candidates:
180 self.restrict = True # do not expand when reading
180 self.restrict = True # do not expand when reading
181 msg = (expand and _('overwriting %s expanding keywords\n')
181 msg = (expand and _('overwriting %s expanding keywords\n')
182 or _('overwriting %s shrinking keywords\n'))
182 or _('overwriting %s shrinking keywords\n'))
183 for f in candidates:
183 for f in candidates:
184 fp = self.repo.file(f)
184 fp = self.repo.file(f)
185 data = fp.read(mf[f])
185 data = fp.read(mf[f])
186 if util.binary(data):
186 if util.binary(data):
187 continue
187 continue
188 if expand:
188 if expand:
189 if node is None:
189 if node is None:
190 ctx = self.repo.filectx(f, fileid=mf[f]).changectx()
190 ctx = self.repo.filectx(f, fileid=mf[f]).changectx()
191 data, found = self.substitute(data, f, ctx,
191 data, found = self.substitute(data, f, ctx,
192 self.re_kw.subn)
192 self.re_kw.subn)
193 else:
193 else:
194 found = self.re_kw.search(data)
194 found = self.re_kw.search(data)
195 if found:
195 if found:
196 notify(msg % f)
196 notify(msg % f)
197 self.repo.wwrite(f, data, mf.flags(f))
197 self.repo.wwrite(f, data, mf.flags(f))
198 self.repo.dirstate.normal(f)
198 self.repo.dirstate.normal(f)
199 self.restrict = False
199 self.restrict = False
200
200
201 def shrinktext(self, text):
201 def shrinktext(self, text):
202 '''Unconditionally removes all keyword substitutions from text.'''
202 '''Unconditionally removes all keyword substitutions from text.'''
203 return self.re_kw.sub(r'$\1$', text)
203 return self.re_kw.sub(r'$\1$', text)
204
204
205 def shrink(self, fname, text):
205 def shrink(self, fname, text):
206 '''Returns text with all keyword substitutions removed.'''
206 '''Returns text with all keyword substitutions removed.'''
207 if self.match(fname) and not util.binary(text):
207 if self.match(fname) and not util.binary(text):
208 return self.shrinktext(text)
208 return self.shrinktext(text)
209 return text
209 return text
210
210
211 def shrinklines(self, fname, lines):
211 def shrinklines(self, fname, lines):
212 '''Returns lines with keyword substitutions removed.'''
212 '''Returns lines with keyword substitutions removed.'''
213 if self.match(fname):
213 if self.match(fname):
214 text = ''.join(lines)
214 text = ''.join(lines)
215 if not util.binary(text):
215 if not util.binary(text):
216 return self.shrinktext(text).splitlines(True)
216 return self.shrinktext(text).splitlines(True)
217 return lines
217 return lines
218
218
219 def wread(self, fname, data):
219 def wread(self, fname, data):
220 '''If in restricted mode returns data read from wdir with
220 '''If in restricted mode returns data read from wdir with
221 keyword substitutions removed.'''
221 keyword substitutions removed.'''
222 return self.restrict and self.shrink(fname, data) or data
222 return self.restrict and self.shrink(fname, data) or data
223
223
224 class kwfilelog(filelog.filelog):
224 class kwfilelog(filelog.filelog):
225 '''
225 '''
226 Subclass of filelog to hook into its read, add, cmp methods.
226 Subclass of filelog to hook into its read, add, cmp methods.
227 Keywords are "stored" unexpanded, and processed on reading.
227 Keywords are "stored" unexpanded, and processed on reading.
228 '''
228 '''
229 def __init__(self, opener, kwt, path):
229 def __init__(self, opener, kwt, path):
230 super(kwfilelog, self).__init__(opener, path)
230 super(kwfilelog, self).__init__(opener, path)
231 self.kwt = kwt
231 self.kwt = kwt
232 self.path = path
232 self.path = path
233
233
234 def read(self, node):
234 def read(self, node):
235 '''Expands keywords when reading filelog.'''
235 '''Expands keywords when reading filelog.'''
236 data = super(kwfilelog, self).read(node)
236 data = super(kwfilelog, self).read(node)
237 return self.kwt.expand(self.path, node, data)
237 return self.kwt.expand(self.path, node, data)
238
238
239 def add(self, text, meta, tr, link, p1=None, p2=None):
239 def add(self, text, meta, tr, link, p1=None, p2=None):
240 '''Removes keyword substitutions when adding to filelog.'''
240 '''Removes keyword substitutions when adding to filelog.'''
241 text = self.kwt.shrink(self.path, text)
241 text = self.kwt.shrink(self.path, text)
242 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
242 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
243
243
244 def cmp(self, node, text):
244 def cmp(self, node, text):
245 '''Removes keyword substitutions for comparison.'''
245 '''Removes keyword substitutions for comparison.'''
246 text = self.kwt.shrink(self.path, text)
246 text = self.kwt.shrink(self.path, text)
247 if self.renamed(node):
247 if self.renamed(node):
248 t2 = super(kwfilelog, self).read(node)
248 t2 = super(kwfilelog, self).read(node)
249 return t2 != text
249 return t2 != text
250 return revlog.revlog.cmp(self, node, text)
250 return revlog.revlog.cmp(self, node, text)
251
251
252 def _status(ui, repo, kwt, unknown, *pats, **opts):
252 def _status(ui, repo, kwt, unknown, *pats, **opts):
253 '''Bails out if [keyword] configuration is not active.
253 '''Bails out if [keyword] configuration is not active.
254 Returns status of working directory.'''
254 Returns status of working directory.'''
255 if kwt:
255 if kwt:
256 match = cmdutil.match(repo, pats, opts)
256 match = cmdutil.match(repo, pats, opts)
257 return repo.status(match=match, unknown=unknown, clean=True)
257 return repo.status(match=match, unknown=unknown, clean=True)
258 if ui.configitems('keyword'):
258 if ui.configitems('keyword'):
259 raise util.Abort(_('[keyword] patterns cannot match'))
259 raise util.Abort(_('[keyword] patterns cannot match'))
260 raise util.Abort(_('no [keyword] patterns configured'))
260 raise util.Abort(_('no [keyword] patterns configured'))
261
261
262 def _kwfwrite(ui, repo, expand, *pats, **opts):
262 def _kwfwrite(ui, repo, expand, *pats, **opts):
263 '''Selects files and passes them to kwtemplater.overwrite.'''
263 '''Selects files and passes them to kwtemplater.overwrite.'''
264 if repo.dirstate.parents()[1] != nullid:
264 if repo.dirstate.parents()[1] != nullid:
265 raise util.Abort(_('outstanding uncommitted merge'))
265 raise util.Abort(_('outstanding uncommitted merge'))
266 kwt = kwtools['templater']
266 kwt = kwtools['templater']
267 status = _status(ui, repo, kwt, False, *pats, **opts)
267 status = _status(ui, repo, kwt, False, *pats, **opts)
268 modified, added, removed, deleted = status[:4]
268 modified, added, removed, deleted = status[:4]
269 if modified or added or removed or deleted:
269 if modified or added or removed or deleted:
270 raise util.Abort(_('outstanding uncommitted changes'))
270 raise util.Abort(_('outstanding uncommitted changes'))
271 wlock = lock = None
271 wlock = lock = None
272 try:
272 try:
273 wlock = repo.wlock()
273 wlock = repo.wlock()
274 lock = repo.lock()
274 lock = repo.lock()
275 kwt.overwrite(None, expand, status[6])
275 kwt.overwrite(None, expand, status[6])
276 finally:
276 finally:
277 release(lock, wlock)
277 release(lock, wlock)
278
278
279 def demo(ui, repo, *args, **opts):
279 def demo(ui, repo, *args, **opts):
280 '''print [keywordmaps] configuration and an expansion example
280 '''print [keywordmaps] configuration and an expansion example
281
281
282 Show current, custom, or default keyword template maps and their
282 Show current, custom, or default keyword template maps and their
283 expansion.
283 expansion.
284
284
285 Extend current configuration by specifying maps as arguments and
285 Extend current configuration by specifying maps as arguments and
286 optionally by reading from an additional hgrc file.
286 optionally by reading from an additional hgrc file.
287
287
288 Override current keyword template maps with "default" option.
288 Override current keyword template maps with "default" option.
289 '''
289 '''
290 def demostatus(stat):
290 def demostatus(stat):
291 ui.status(_('\n\t%s\n') % stat)
291 ui.status(_('\n\t%s\n') % stat)
292
292
293 def demoitems(section, items):
293 def demoitems(section, items):
294 ui.write('[%s]\n' % section)
294 ui.write('[%s]\n' % section)
295 for k, v in items:
295 for k, v in items:
296 ui.write('%s = %s\n' % (k, v))
296 ui.write('%s = %s\n' % (k, v))
297
297
298 msg = 'hg keyword config and expansion example'
298 msg = 'hg keyword config and expansion example'
299 kwstatus = 'current'
299 kwstatus = 'current'
300 fn = 'demo.txt'
300 fn = 'demo.txt'
301 branchname = 'demobranch'
301 branchname = 'demobranch'
302 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
302 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
303 ui.note(_('creating temporary repository at %s\n') % tmpdir)
303 ui.note(_('creating temporary repository at %s\n') % tmpdir)
304 repo = localrepo.localrepository(ui, tmpdir, True)
304 repo = localrepo.localrepository(ui, tmpdir, True)
305 ui.setconfig('keyword', fn, '')
305 ui.setconfig('keyword', fn, '')
306 if args or opts.get('rcfile'):
306 if args or opts.get('rcfile'):
307 kwstatus = 'custom'
307 kwstatus = 'custom'
308 if opts.get('rcfile'):
308 if opts.get('rcfile'):
309 ui.readconfig(opts.get('rcfile'))
309 ui.readconfig(opts.get('rcfile'))
310 if opts.get('default'):
310 if opts.get('default'):
311 kwstatus = 'default'
311 kwstatus = 'default'
312 kwmaps = kwtemplater.templates
312 kwmaps = kwtemplater.templates
313 if ui.configitems('keywordmaps'):
313 if ui.configitems('keywordmaps'):
314 # override maps from optional rcfile
314 # override maps from optional rcfile
315 for k, v in kwmaps.iteritems():
315 for k, v in kwmaps.iteritems():
316 ui.setconfig('keywordmaps', k, v)
316 ui.setconfig('keywordmaps', k, v)
317 elif args:
317 elif args:
318 # simulate hgrc parsing
318 # simulate hgrc parsing
319 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
319 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
320 fp = repo.opener('hgrc', 'w')
320 fp = repo.opener('hgrc', 'w')
321 fp.writelines(rcmaps)
321 fp.writelines(rcmaps)
322 fp.close()
322 fp.close()
323 ui.readconfig(repo.join('hgrc'))
323 ui.readconfig(repo.join('hgrc'))
324 if not opts.get('default'):
324 if not opts.get('default'):
325 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
325 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
326 uisetup(ui)
326 uisetup(ui)
327 reposetup(ui, repo)
327 reposetup(ui, repo)
328 for k, v in ui.configitems('extensions'):
328 for k, v in ui.configitems('extensions'):
329 if k.endswith('keyword'):
329 if k.endswith('keyword'):
330 extension = '%s = %s' % (k, v)
330 extension = '%s = %s' % (k, v)
331 break
331 break
332 demostatus('config using %s keyword template maps' % kwstatus)
332 demostatus('config using %s keyword template maps' % kwstatus)
333 ui.write('[extensions]\n%s\n' % extension)
333 ui.write('[extensions]\n%s\n' % extension)
334 demoitems('keyword', ui.configitems('keyword'))
334 demoitems('keyword', ui.configitems('keyword'))
335 demoitems('keywordmaps', kwmaps.iteritems())
335 demoitems('keywordmaps', kwmaps.iteritems())
336 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
336 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
337 repo.wopener(fn, 'w').write(keywords)
337 repo.wopener(fn, 'w').write(keywords)
338 repo.add([fn])
338 repo.add([fn])
339 path = repo.wjoin(fn)
339 path = repo.wjoin(fn)
340 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
340 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
341 ui.note(keywords)
341 ui.note(keywords)
342 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
342 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
343 # silence branch command if not verbose
343 # silence branch command if not verbose
344 quiet = ui.quiet
344 quiet = ui.quiet
345 ui.quiet = not ui.verbose
345 ui.quiet = not ui.verbose
346 commands.branch(ui, repo, branchname)
346 commands.branch(ui, repo, branchname)
347 ui.quiet = quiet
347 ui.quiet = quiet
348 for name, cmd in ui.configitems('hooks'):
348 for name, cmd in ui.configitems('hooks'):
349 if name.split('.', 1)[0].find('commit') > -1:
349 if name.split('.', 1)[0].find('commit') > -1:
350 repo.ui.setconfig('hooks', name, '')
350 repo.ui.setconfig('hooks', name, '')
351 ui.note(_('unhooked all commit hooks\n'))
351 ui.note(_('unhooked all commit hooks\n'))
352 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
352 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
353 repo.commit(text=msg)
353 repo.commit(text=msg)
354 fmt = ui.verbose and ' in %s' % path or ''
354 fmt = ui.verbose and ' in %s' % path or ''
355 demostatus('%s keywords expanded%s' % (kwstatus, fmt))
355 demostatus('%s keywords expanded%s' % (kwstatus, fmt))
356 ui.write(repo.wread(fn))
356 ui.write(repo.wread(fn))
357 ui.debug(_('\nremoving temporary repository %s\n') % tmpdir)
357 ui.debug(_('\nremoving temporary repository %s\n') % tmpdir)
358 shutil.rmtree(tmpdir, ignore_errors=True)
358 shutil.rmtree(tmpdir, ignore_errors=True)
359
359
360 def expand(ui, repo, *pats, **opts):
360 def expand(ui, repo, *pats, **opts):
361 '''expand keywords in working directory
361 '''expand keywords in working directory
362
362
363 Run after (re)enabling keyword expansion.
363 Run after (re)enabling keyword expansion.
364
364
365 kwexpand refuses to run if given files contain local changes.
365 kwexpand refuses to run if given files contain local changes.
366 '''
366 '''
367 # 3rd argument sets expansion to True
367 # 3rd argument sets expansion to True
368 _kwfwrite(ui, repo, True, *pats, **opts)
368 _kwfwrite(ui, repo, True, *pats, **opts)
369
369
370 def files(ui, repo, *pats, **opts):
370 def files(ui, repo, *pats, **opts):
371 '''print files currently configured for keyword expansion
371 '''print files currently configured for keyword expansion
372
372
373 Crosscheck which files in working directory are potential targets
373 Crosscheck which files in working directory are potential targets
374 for keyword expansion. That is, files matched by [keyword] config
374 for keyword expansion. That is, files matched by [keyword] config
375 patterns but not symlinks.
375 patterns but not symlinks.
376 '''
376 '''
377 kwt = kwtools['templater']
377 kwt = kwtools['templater']
378 status = _status(ui, repo, kwt, opts.get('untracked'), *pats, **opts)
378 status = _status(ui, repo, kwt, opts.get('untracked'), *pats, **opts)
379 modified, added, removed, deleted, unknown, ignored, clean = status
379 modified, added, removed, deleted, unknown, ignored, clean = status
380 files = sorted(modified + added + clean + unknown)
380 files = sorted(modified + added + clean + unknown)
381 wctx = repo[None]
381 wctx = repo[None]
382 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
382 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
383 cwd = pats and repo.getcwd() or ''
383 cwd = pats and repo.getcwd() or ''
384 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
384 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
385 if opts.get('all') or opts.get('ignore'):
385 if opts.get('all') or opts.get('ignore'):
386 kwfstats += (('I', [f for f in files if f not in kwfiles]),)
386 kwfstats += (('I', [f for f in files if f not in kwfiles]),)
387 for char, filenames in kwfstats:
387 for char, filenames in kwfstats:
388 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
388 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
389 for f in filenames:
389 for f in filenames:
390 ui.write(fmt % repo.pathto(f, cwd))
390 ui.write(fmt % repo.pathto(f, cwd))
391
391
392 def shrink(ui, repo, *pats, **opts):
392 def shrink(ui, repo, *pats, **opts):
393 '''revert expanded keywords in working directory
393 '''revert expanded keywords in working directory
394
394
395 Run before changing/disabling active keywords or if you experience
395 Run before changing/disabling active keywords or if you experience
396 problems with "hg import" or "hg merge".
396 problems with "hg import" or "hg merge".
397
397
398 kwshrink refuses to run if given files contain local changes.
398 kwshrink refuses to run if given files contain local changes.
399 '''
399 '''
400 # 3rd argument sets expansion to False
400 # 3rd argument sets expansion to False
401 _kwfwrite(ui, repo, False, *pats, **opts)
401 _kwfwrite(ui, repo, False, *pats, **opts)
402
402
403
403
404 def uisetup(ui):
404 def uisetup(ui):
405 '''Collects [keyword] config in kwtools.
405 '''Collects [keyword] config in kwtools.
406 Monkeypatches dispatch._parse if needed.'''
406 Monkeypatches dispatch._parse if needed.'''
407
407
408 for pat, opt in ui.configitems('keyword'):
408 for pat, opt in ui.configitems('keyword'):
409 if opt != 'ignore':
409 if opt != 'ignore':
410 kwtools['inc'].append(pat)
410 kwtools['inc'].append(pat)
411 else:
411 else:
412 kwtools['exc'].append(pat)
412 kwtools['exc'].append(pat)
413
413
414 if kwtools['inc']:
414 if kwtools['inc']:
415 def kwdispatch_parse(orig, ui, args):
415 def kwdispatch_parse(orig, ui, args):
416 '''Monkeypatch dispatch._parse to obtain running hg command.'''
416 '''Monkeypatch dispatch._parse to obtain running hg command.'''
417 cmd, func, args, options, cmdoptions = orig(ui, args)
417 cmd, func, args, options, cmdoptions = orig(ui, args)
418 kwtools['hgcmd'] = cmd
418 kwtools['hgcmd'] = cmd
419 return cmd, func, args, options, cmdoptions
419 return cmd, func, args, options, cmdoptions
420
420
421 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
421 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
422
422
423 def reposetup(ui, repo):
423 def reposetup(ui, repo):
424 '''Sets up repo as kwrepo for keyword substitution.
424 '''Sets up repo as kwrepo for keyword substitution.
425 Overrides file method to return kwfilelog instead of filelog
425 Overrides file method to return kwfilelog instead of filelog
426 if file matches user configuration.
426 if file matches user configuration.
427 Wraps commit to overwrite configured files with updated
427 Wraps commit to overwrite configured files with updated
428 keyword substitutions.
428 keyword substitutions.
429 Monkeypatches patch and webcommands.'''
429 Monkeypatches patch and webcommands.'''
430
430
431 try:
431 try:
432 if (not repo.local() or not kwtools['inc']
432 if (not repo.local() or not kwtools['inc']
433 or kwtools['hgcmd'] in nokwcommands.split()
433 or kwtools['hgcmd'] in nokwcommands.split()
434 or '.hg' in util.splitpath(repo.root)
434 or '.hg' in util.splitpath(repo.root)
435 or repo._url.startswith('bundle:')):
435 or repo._url.startswith('bundle:')):
436 return
436 return
437 except AttributeError:
437 except AttributeError:
438 pass
438 pass
439
439
440 kwtools['templater'] = kwt = kwtemplater(ui, repo)
440 kwtools['templater'] = kwt = kwtemplater(ui, repo)
441
441
442 class kwrepo(repo.__class__):
442 class kwrepo(repo.__class__):
443 def file(self, f):
443 def file(self, f):
444 if f[0] == '/':
444 if f[0] == '/':
445 f = f[1:]
445 f = f[1:]
446 return kwfilelog(self.sopener, kwt, f)
446 return kwfilelog(self.sopener, kwt, f)
447
447
448 def wread(self, filename):
448 def wread(self, filename):
449 data = super(kwrepo, self).wread(filename)
449 data = super(kwrepo, self).wread(filename)
450 return kwt.wread(filename, data)
450 return kwt.wread(filename, data)
451
451
452 def commit(self, files=None, text='', user=None, date=None,
452 def commit(self, files=None, text='', user=None, date=None,
453 match=None, force=False, editor=None, extra={}):
453 match=None, force=False, editor=None, extra={}):
454 wlock = lock = None
454 wlock = lock = None
455 _p1 = _p2 = None
455 _p1 = _p2 = None
456 try:
456 try:
457 wlock = self.wlock()
457 wlock = self.wlock()
458 lock = self.lock()
458 lock = self.lock()
459 # store and postpone commit hooks
459 # store and postpone commit hooks
460 commithooks = {}
460 commithooks = {}
461 for name, cmd in ui.configitems('hooks'):
461 for name, cmd in ui.configitems('hooks'):
462 if name.split('.', 1)[0] == 'commit':
462 if name.split('.', 1)[0] == 'commit':
463 commithooks[name] = cmd
463 commithooks[name] = cmd
464 ui.setconfig('hooks', name, None)
464 ui.setconfig('hooks', name, None)
465 if commithooks:
465 if commithooks:
466 # store parents for commit hook environment
466 # store parents for commit hook environment
467 _p1, _p2 = repo.dirstate.parents()
467 _p1, _p2 = repo.dirstate.parents()
468 _p1 = hex(_p1)
468 _p1 = hex(_p1)
469 if _p2 == nullid:
469 if _p2 == nullid:
470 _p2 = ''
470 _p2 = ''
471 else:
471 else:
472 _p2 = hex(_p2)
472 _p2 = hex(_p2)
473
473
474 n = super(kwrepo, self).commit(files, text, user, date, match,
474 n = super(kwrepo, self).commit(files, text, user, date, match,
475 force, editor, extra)
475 force, editor, extra)
476
476
477 # restore commit hooks
477 # restore commit hooks
478 for name, cmd in commithooks.iteritems():
478 for name, cmd in commithooks.iteritems():
479 ui.setconfig('hooks', name, cmd)
479 ui.setconfig('hooks', name, cmd)
480 if n is not None:
480 if n is not None:
481 kwt.overwrite(n, True, None)
481 kwt.overwrite(n, True, None)
482 repo.hook('commit', node=n, parent1=_p1, parent2=_p2)
482 repo.hook('commit', node=n, parent1=_p1, parent2=_p2)
483 return n
483 return n
484 finally:
484 finally:
485 release(lock, wlock)
485 release(lock, wlock)
486
486
487 # monkeypatches
487 # monkeypatches
488 def kwpatchfile_init(orig, self, ui, fname, opener, missing=False):
488 def kwpatchfile_init(orig, self, ui, fname, opener, missing=False):
489 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
489 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
490 rejects or conflicts due to expanded keywords in working dir.'''
490 rejects or conflicts due to expanded keywords in working dir.'''
491 orig(self, ui, fname, opener, missing)
491 orig(self, ui, fname, opener, missing)
492 # shrink keywords read from working dir
492 # shrink keywords read from working dir
493 self.lines = kwt.shrinklines(self.fname, self.lines)
493 self.lines = kwt.shrinklines(self.fname, self.lines)
494
494
495 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
495 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
496 opts=None):
496 opts=None):
497 '''Monkeypatch patch.diff to avoid expansion except when
497 '''Monkeypatch patch.diff to avoid expansion except when
498 comparing against working dir.'''
498 comparing against working dir.'''
499 if node2 is not None:
499 if node2 is not None:
500 kwt.match = util.never
500 kwt.match = util.never
501 elif node1 is not None and node1 != repo['.'].node():
501 elif node1 is not None and node1 != repo['.'].node():
502 kwt.restrict = True
502 kwt.restrict = True
503 return orig(repo, node1, node2, match, changes, opts)
503 return orig(repo, node1, node2, match, changes, opts)
504
504
505 def kwweb_skip(orig, web, req, tmpl):
505 def kwweb_skip(orig, web, req, tmpl):
506 '''Wraps webcommands.x turning off keyword expansion.'''
506 '''Wraps webcommands.x turning off keyword expansion.'''
507 kwt.match = util.never
507 kwt.match = util.never
508 return orig(web, req, tmpl)
508 return orig(web, req, tmpl)
509
509
510 repo.__class__ = kwrepo
510 repo.__class__ = kwrepo
511
511
512 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
512 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
513 extensions.wrapfunction(patch, 'diff', kw_diff)
513 extensions.wrapfunction(patch, 'diff', kw_diff)
514 for c in 'annotate changeset rev filediff diff'.split():
514 for c in 'annotate changeset rev filediff diff'.split():
515 extensions.wrapfunction(webcommands, c, kwweb_skip)
515 extensions.wrapfunction(webcommands, c, kwweb_skip)
516
516
517 cmdtable = {
517 cmdtable = {
518 'kwdemo':
518 'kwdemo':
519 (demo,
519 (demo,
520 [('d', 'default', None, _('show default keyword template maps')),
520 [('d', 'default', None, _('show default keyword template maps')),
521 ('f', 'rcfile', [], _('read maps from rcfile'))],
521 ('f', 'rcfile', [], _('read maps from rcfile'))],
522 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
522 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
523 'kwexpand': (expand, commands.walkopts,
523 'kwexpand': (expand, commands.walkopts,
524 _('hg kwexpand [OPTION]... [FILE]...')),
524 _('hg kwexpand [OPTION]... [FILE]...')),
525 'kwfiles':
525 'kwfiles':
526 (files,
526 (files,
527 [('a', 'all', None, _('show keyword status flags of all files')),
527 [('a', 'all', None, _('show keyword status flags of all files')),
528 ('i', 'ignore', None, _('show files excluded from expansion')),
528 ('i', 'ignore', None, _('show files excluded from expansion')),
529 ('u', 'untracked', None, _('additionally show untracked files')),
529 ('u', 'untracked', None, _('additionally show untracked files')),
530 ] + commands.walkopts,
530 ] + commands.walkopts,
531 _('hg kwfiles [OPTION]... [FILE]...')),
531 _('hg kwfiles [OPTION]... [FILE]...')),
532 'kwshrink': (shrink, commands.walkopts,
532 'kwshrink': (shrink, commands.walkopts,
533 _('hg kwshrink [OPTION]... [FILE]...')),
533 _('hg kwshrink [OPTION]... [FILE]...')),
534 }
534 }
@@ -1,126 +1,126 b''
1 # win32mbcs.py -- MBCS filename support for Mercurial
1 # win32mbcs.py -- MBCS filename support for Mercurial
2 #
2 #
3 # Copyright (c) 2008 Shun-ichi Goto <shunichi.goto@gmail.com>
3 # Copyright (c) 2008 Shun-ichi Goto <shunichi.goto@gmail.com>
4 #
4 #
5 # Version: 0.2
5 # Version: 0.2
6 # Author: Shun-ichi Goto <shunichi.goto@gmail.com>
6 # Author: Shun-ichi Goto <shunichi.goto@gmail.com>
7 #
7 #
8 # This software may be used and distributed according to the terms of the
8 # This software may be used and distributed according to the terms of the
9 # GNU General Public License version 2, incorporated herein by reference.
9 # GNU General Public License version 2, incorporated herein by reference.
10 #
10 #
11
11
12 """allow to use MBCS path with problematic encoding.
12 """allow to use MBCS path with problematic encoding.
13
13
14 Some MBCS encodings are not good for some path operations (i.e.
14 Some MBCS encodings are not good for some path operations (i.e.
15 splitting path, case conversion, etc.) with its encoded bytes. We call
15 splitting path, case conversion, etc.) with its encoded bytes. We call
16 such a encoding (i.e. shift_jis and big5) as "problematic encoding".
16 such a encoding (i.e. shift_jis and big5) as "problematic encoding".
17 This extension can be used to fix the issue with those encodings by
17 This extension can be used to fix the issue with those encodings by
18 wrapping some functions to convert to Unicode string before path
18 wrapping some functions to convert to Unicode string before path
19 operation.
19 operation.
20
20
21 This extension is usefull for:
21 This extension is useful for:
22 * Japanese Windows users using shift_jis encoding.
22 * Japanese Windows users using shift_jis encoding.
23 * Chinese Windows users using big5 encoding.
23 * Chinese Windows users using big5 encoding.
24 * All users who use a repository with one of problematic encodings on
24 * All users who use a repository with one of problematic encodings on
25 case-insensitive file system.
25 case-insensitive file system.
26
26
27 This extension is not needed for:
27 This extension is not needed for:
28 * Any user who use only ASCII chars in path.
28 * Any user who use only ASCII chars in path.
29 * Any user who do not use any of problematic encodings.
29 * Any user who do not use any of problematic encodings.
30
30
31 Note that there are some limitations on using this extension:
31 Note that there are some limitations on using this extension:
32 * You should use single encoding in one repository.
32 * You should use single encoding in one repository.
33 * You should set same encoding for the repository by locale or
33 * You should set same encoding for the repository by locale or
34 HGENCODING.
34 HGENCODING.
35
35
36 To use this extension, enable the extension in .hg/hgrc or ~/.hgrc:
36 To use this extension, enable the extension in .hg/hgrc or ~/.hgrc:
37
37
38 [extensions]
38 [extensions]
39 hgext.win32mbcs =
39 hgext.win32mbcs =
40
40
41 Path encoding conversion are done between Unicode and
41 Path encoding conversion are done between Unicode and
42 encoding.encoding which is decided by mercurial from current locale
42 encoding.encoding which is decided by mercurial from current locale
43 setting or HGENCODING.
43 setting or HGENCODING.
44
44
45 """
45 """
46
46
47 import os
47 import os
48 from mercurial.i18n import _
48 from mercurial.i18n import _
49 from mercurial import util, encoding
49 from mercurial import util, encoding
50
50
51 def decode(arg):
51 def decode(arg):
52 if isinstance(arg, str):
52 if isinstance(arg, str):
53 uarg = arg.decode(encoding.encoding)
53 uarg = arg.decode(encoding.encoding)
54 if arg == uarg.encode(encoding.encoding):
54 if arg == uarg.encode(encoding.encoding):
55 return uarg
55 return uarg
56 raise UnicodeError("Not local encoding")
56 raise UnicodeError("Not local encoding")
57 elif isinstance(arg, tuple):
57 elif isinstance(arg, tuple):
58 return tuple(map(decode, arg))
58 return tuple(map(decode, arg))
59 elif isinstance(arg, list):
59 elif isinstance(arg, list):
60 return map(decode, arg)
60 return map(decode, arg)
61 return arg
61 return arg
62
62
63 def encode(arg):
63 def encode(arg):
64 if isinstance(arg, unicode):
64 if isinstance(arg, unicode):
65 return arg.encode(encoding.encoding)
65 return arg.encode(encoding.encoding)
66 elif isinstance(arg, tuple):
66 elif isinstance(arg, tuple):
67 return tuple(map(encode, arg))
67 return tuple(map(encode, arg))
68 elif isinstance(arg, list):
68 elif isinstance(arg, list):
69 return map(encode, arg)
69 return map(encode, arg)
70 return arg
70 return arg
71
71
72 def wrapper(func, args):
72 def wrapper(func, args):
73 # check argument is unicode, then call original
73 # check argument is unicode, then call original
74 for arg in args:
74 for arg in args:
75 if isinstance(arg, unicode):
75 if isinstance(arg, unicode):
76 return func(*args)
76 return func(*args)
77
77
78 try:
78 try:
79 # convert arguments to unicode, call func, then convert back
79 # convert arguments to unicode, call func, then convert back
80 return encode(func(*decode(args)))
80 return encode(func(*decode(args)))
81 except UnicodeError:
81 except UnicodeError:
82 # If not encoded with encoding.encoding, report it then
82 # If not encoded with encoding.encoding, report it then
83 # continue with calling original function.
83 # continue with calling original function.
84 raise util.Abort(_("[win32mbcs] filename conversion fail with"
84 raise util.Abort(_("[win32mbcs] filename conversion fail with"
85 " %s encoding\n") % (encoding.encoding))
85 " %s encoding\n") % (encoding.encoding))
86
86
87 def wrapname(name):
87 def wrapname(name):
88 idx = name.rfind('.')
88 idx = name.rfind('.')
89 module = name[:idx]
89 module = name[:idx]
90 name = name[idx+1:]
90 name = name[idx+1:]
91 module = globals()[module]
91 module = globals()[module]
92 func = getattr(module, name)
92 func = getattr(module, name)
93 def f(*args):
93 def f(*args):
94 return wrapper(func, args)
94 return wrapper(func, args)
95 try:
95 try:
96 f.__name__ = func.__name__ # fail with python23
96 f.__name__ = func.__name__ # fail with python23
97 except Exception:
97 except Exception:
98 pass
98 pass
99 setattr(module, name, f)
99 setattr(module, name, f)
100
100
101 # List of functions to be wrapped.
101 # List of functions to be wrapped.
102 # NOTE: os.path.dirname() and os.path.basename() are safe because
102 # NOTE: os.path.dirname() and os.path.basename() are safe because
103 # they use result of os.path.split()
103 # they use result of os.path.split()
104 funcs = '''os.path.join os.path.split os.path.splitext
104 funcs = '''os.path.join os.path.split os.path.splitext
105 os.path.splitunc os.path.normpath os.path.normcase os.makedirs
105 os.path.splitunc os.path.normpath os.path.normcase os.makedirs
106 util.endswithsep util.splitpath util.checkcase util.fspath'''
106 util.endswithsep util.splitpath util.checkcase util.fspath'''
107
107
108 # codec and alias names of sjis and big5 to be faked.
108 # codec and alias names of sjis and big5 to be faked.
109 problematic_encodings = '''big5 big5-tw csbig5 big5hkscs big5-hkscs
109 problematic_encodings = '''big5 big5-tw csbig5 big5hkscs big5-hkscs
110 hkscs cp932 932 ms932 mskanji ms-kanji shift_jis csshiftjis shiftjis
110 hkscs cp932 932 ms932 mskanji ms-kanji shift_jis csshiftjis shiftjis
111 sjis s_jis shift_jis_2004 shiftjis2004 sjis_2004 sjis2004
111 sjis s_jis shift_jis_2004 shiftjis2004 sjis_2004 sjis2004
112 shift_jisx0213 shiftjisx0213 sjisx0213 s_jisx0213'''
112 shift_jisx0213 shiftjisx0213 sjisx0213 s_jisx0213'''
113
113
114 def reposetup(ui, repo):
114 def reposetup(ui, repo):
115 # TODO: decide use of config section for this extension
115 # TODO: decide use of config section for this extension
116 if not os.path.supports_unicode_filenames:
116 if not os.path.supports_unicode_filenames:
117 ui.warn(_("[win32mbcs] cannot activate on this platform.\n"))
117 ui.warn(_("[win32mbcs] cannot activate on this platform.\n"))
118 return
118 return
119
119
120 # fake is only for relevant environment.
120 # fake is only for relevant environment.
121 if encoding.encoding.lower() in problematic_encodings.split():
121 if encoding.encoding.lower() in problematic_encodings.split():
122 for f in funcs.split():
122 for f in funcs.split():
123 wrapname(f)
123 wrapname(f)
124 ui.debug(_("[win32mbcs] activated with encoding: %s\n")
124 ui.debug(_("[win32mbcs] activated with encoding: %s\n")
125 % encoding.encoding)
125 % encoding.encoding)
126
126
@@ -1,421 +1,421 b''
1 # help.py - help data for mercurial
1 # help.py - help data for mercurial
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 Matt Mackall <mpm@selenic.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 from i18n import _
8 from i18n import _
9
9
10 helptable = (
10 helptable = (
11 (["dates"], _("Date Formats"),
11 (["dates"], _("Date Formats"),
12 _(r'''
12 _(r'''
13 Some commands allow the user to specify a date, e.g.:
13 Some commands allow the user to specify a date, e.g.:
14 * backout, commit, import, tag: Specify the commit date.
14 * backout, commit, import, tag: Specify the commit date.
15 * log, revert, update: Select revision(s) by date.
15 * log, revert, update: Select revision(s) by date.
16
16
17 Many date formats are valid. Here are some examples:
17 Many date formats are valid. Here are some examples:
18
18
19 "Wed Dec 6 13:18:29 2006" (local timezone assumed)
19 "Wed Dec 6 13:18:29 2006" (local timezone assumed)
20 "Dec 6 13:18 -0600" (year assumed, time offset provided)
20 "Dec 6 13:18 -0600" (year assumed, time offset provided)
21 "Dec 6 13:18 UTC" (UTC and GMT are aliases for +0000)
21 "Dec 6 13:18 UTC" (UTC and GMT are aliases for +0000)
22 "Dec 6" (midnight)
22 "Dec 6" (midnight)
23 "13:18" (today assumed)
23 "13:18" (today assumed)
24 "3:39" (3:39AM assumed)
24 "3:39" (3:39AM assumed)
25 "3:39pm" (15:39)
25 "3:39pm" (15:39)
26 "2006-12-06 13:18:29" (ISO 8601 format)
26 "2006-12-06 13:18:29" (ISO 8601 format)
27 "2006-12-6 13:18"
27 "2006-12-6 13:18"
28 "2006-12-6"
28 "2006-12-6"
29 "12-6"
29 "12-6"
30 "12/6"
30 "12/6"
31 "12/6/6" (Dec 6 2006)
31 "12/6/6" (Dec 6 2006)
32
32
33 Lastly, there is Mercurial's internal format:
33 Lastly, there is Mercurial's internal format:
34
34
35 "1165432709 0" (Wed Dec 6 13:18:29 2006 UTC)
35 "1165432709 0" (Wed Dec 6 13:18:29 2006 UTC)
36
36
37 This is the internal representation format for dates. unixtime is
37 This is the internal representation format for dates. unixtime is
38 the number of seconds since the epoch (1970-01-01 00:00 UTC).
38 the number of seconds since the epoch (1970-01-01 00:00 UTC).
39 offset is the offset of the local timezone, in seconds west of UTC
39 offset is the offset of the local timezone, in seconds west of UTC
40 (negative if the timezone is east of UTC).
40 (negative if the timezone is east of UTC).
41
41
42 The log command also accepts date ranges:
42 The log command also accepts date ranges:
43
43
44 "<{datetime}" - at or before a given date/time
44 "<{datetime}" - at or before a given date/time
45 ">{datetime}" - on or after a given date/time
45 ">{datetime}" - on or after a given date/time
46 "{datetime} to {datetime}" - a date range, inclusive
46 "{datetime} to {datetime}" - a date range, inclusive
47 "-{days}" - within a given number of days of today
47 "-{days}" - within a given number of days of today
48 ''')),
48 ''')),
49
49
50 (["patterns"], _("File Name Patterns"),
50 (["patterns"], _("File Name Patterns"),
51 _(r'''
51 _(r'''
52 Mercurial accepts several notations for identifying one or more
52 Mercurial accepts several notations for identifying one or more
53 files at a time.
53 files at a time.
54
54
55 By default, Mercurial treats filenames as shell-style extended
55 By default, Mercurial treats filenames as shell-style extended
56 glob patterns.
56 glob patterns.
57
57
58 Alternate pattern notations must be specified explicitly.
58 Alternate pattern notations must be specified explicitly.
59
59
60 To use a plain path name without any pattern matching, start it
60 To use a plain path name without any pattern matching, start it
61 with "path:". These path names must completely match starting at
61 with "path:". These path names must completely match starting at
62 the current repository root.
62 the current repository root.
63
63
64 To use an extended glob, start a name with "glob:". Globs are
64 To use an extended glob, start a name with "glob:". Globs are
65 rooted at the current directory; a glob such as "*.c" will only
65 rooted at the current directory; a glob such as "*.c" will only
66 match files in the current directory ending with ".c".
66 match files in the current directory ending with ".c".
67
67
68 The supported glob syntax extensions are "**" to match any string
68 The supported glob syntax extensions are "**" to match any string
69 across path separators and "{a,b}" to mean "a or b".
69 across path separators and "{a,b}" to mean "a or b".
70
70
71 To use a Perl/Python regular expression, start a name with "re:".
71 To use a Perl/Python regular expression, start a name with "re:".
72 Regexp pattern matching is anchored at the root of the repository.
72 Regexp pattern matching is anchored at the root of the repository.
73
73
74 Plain examples:
74 Plain examples:
75
75
76 path:foo/bar a name bar in a directory named foo in the root of
76 path:foo/bar a name bar in a directory named foo in the root of
77 the repository
77 the repository
78 path:path:name a file or directory named "path:name"
78 path:path:name a file or directory named "path:name"
79
79
80 Glob examples:
80 Glob examples:
81
81
82 glob:*.c any name ending in ".c" in the current directory
82 glob:*.c any name ending in ".c" in the current directory
83 *.c any name ending in ".c" in the current directory
83 *.c any name ending in ".c" in the current directory
84 **.c any name ending in ".c" in any subdirectory of the
84 **.c any name ending in ".c" in any subdirectory of the
85 current directory including itself.
85 current directory including itself.
86 foo/*.c any name ending in ".c" in the directory foo
86 foo/*.c any name ending in ".c" in the directory foo
87 foo/**.c any name ending in ".c" in any subdirectory of foo
87 foo/**.c any name ending in ".c" in any subdirectory of foo
88 including itself.
88 including itself.
89
89
90 Regexp examples:
90 Regexp examples:
91
91
92 re:.*\.c$ any name ending in ".c", anywhere in the repository
92 re:.*\.c$ any name ending in ".c", anywhere in the repository
93
93
94 ''')),
94 ''')),
95
95
96 (['environment', 'env'], _('Environment Variables'),
96 (['environment', 'env'], _('Environment Variables'),
97 _(r'''
97 _(r'''
98 HG::
98 HG::
99 Path to the 'hg' executable, automatically passed when running
99 Path to the 'hg' executable, automatically passed when running
100 hooks, extensions or external tools. If unset or empty, this is
100 hooks, extensions or external tools. If unset or empty, this is
101 the hg executable's name if it's frozen, or an executable named
101 the hg executable's name if it's frozen, or an executable named
102 'hg' (with %PATHEXT% [defaulting to COM/EXE/BAT/CMD] extensions on
102 'hg' (with %PATHEXT% [defaulting to COM/EXE/BAT/CMD] extensions on
103 Windows) is searched.
103 Windows) is searched.
104
104
105 HGEDITOR::
105 HGEDITOR::
106 This is the name of the editor to run when committing. See EDITOR.
106 This is the name of the editor to run when committing. See EDITOR.
107
107
108 (deprecated, use .hgrc)
108 (deprecated, use .hgrc)
109
109
110 HGENCODING::
110 HGENCODING::
111 This overrides the default locale setting detected by Mercurial.
111 This overrides the default locale setting detected by Mercurial.
112 This setting is used to convert data including usernames,
112 This setting is used to convert data including usernames,
113 changeset descriptions, tag names, and branches. This setting can
113 changeset descriptions, tag names, and branches. This setting can
114 be overridden with the --encoding command-line option.
114 be overridden with the --encoding command-line option.
115
115
116 HGENCODINGMODE::
116 HGENCODINGMODE::
117 This sets Mercurial's behavior for handling unknown characters
117 This sets Mercurial's behavior for handling unknown characters
118 while transcoding user input. The default is "strict", which
118 while transcoding user input. The default is "strict", which
119 causes Mercurial to abort if it can't map a character. Other
119 causes Mercurial to abort if it can't map a character. Other
120 settings include "replace", which replaces unknown characters, and
120 settings include "replace", which replaces unknown characters, and
121 "ignore", which drops them. This setting can be overridden with
121 "ignore", which drops them. This setting can be overridden with
122 the --encodingmode command-line option.
122 the --encodingmode command-line option.
123
123
124 HGMERGE::
124 HGMERGE::
125 An executable to use for resolving merge conflicts. The program
125 An executable to use for resolving merge conflicts. The program
126 will be executed with three arguments: local file, remote file,
126 will be executed with three arguments: local file, remote file,
127 ancestor file.
127 ancestor file.
128
128
129 (deprecated, use .hgrc)
129 (deprecated, use .hgrc)
130
130
131 HGRCPATH::
131 HGRCPATH::
132 A list of files or directories to search for hgrc files. Item
132 A list of files or directories to search for hgrc files. Item
133 separator is ":" on Unix, ";" on Windows. If HGRCPATH is not set,
133 separator is ":" on Unix, ";" on Windows. If HGRCPATH is not set,
134 platform default search path is used. If empty, only the .hg/hgrc
134 platform default search path is used. If empty, only the .hg/hgrc
135 from the current repository is read.
135 from the current repository is read.
136
136
137 For each element in HGRCPATH:
137 For each element in HGRCPATH:
138 * if it's a directory, all files ending with .rc are added
138 * if it's a directory, all files ending with .rc are added
139 * otherwise, the file itself will be added
139 * otherwise, the file itself will be added
140
140
141 HGUSER::
141 HGUSER::
142 This is the string used as the author of a commit. If not set,
142 This is the string used as the author of a commit. If not set,
143 available values will be considered in this order:
143 available values will be considered in this order:
144
144
145 * HGUSER (deprecated)
145 * HGUSER (deprecated)
146 * hgrc files from the HGRCPATH
146 * hgrc files from the HGRCPATH
147 * EMAIL
147 * EMAIL
148 * interactive prompt
148 * interactive prompt
149 * LOGNAME (with '@hostname' appended)
149 * LOGNAME (with '@hostname' appended)
150
150
151 (deprecated, use .hgrc)
151 (deprecated, use .hgrc)
152
152
153 EMAIL::
153 EMAIL::
154 May be used as the author of a commit; see HGUSER.
154 May be used as the author of a commit; see HGUSER.
155
155
156 LOGNAME::
156 LOGNAME::
157 May be used as the author of a commit; see HGUSER.
157 May be used as the author of a commit; see HGUSER.
158
158
159 VISUAL::
159 VISUAL::
160 This is the name of the editor to use when committing. See EDITOR.
160 This is the name of the editor to use when committing. See EDITOR.
161
161
162 EDITOR::
162 EDITOR::
163 Sometimes Mercurial needs to open a text file in an editor for a
163 Sometimes Mercurial needs to open a text file in an editor for a
164 user to modify, for example when writing commit messages. The
164 user to modify, for example when writing commit messages. The
165 editor it uses is determined by looking at the environment
165 editor it uses is determined by looking at the environment
166 variables HGEDITOR, VISUAL and EDITOR, in that order. The first
166 variables HGEDITOR, VISUAL and EDITOR, in that order. The first
167 non-empty one is chosen. If all of them are empty, the editor
167 non-empty one is chosen. If all of them are empty, the editor
168 defaults to 'vi'.
168 defaults to 'vi'.
169
169
170 PYTHONPATH::
170 PYTHONPATH::
171 This is used by Python to find imported modules and may need to be
171 This is used by Python to find imported modules and may need to be
172 set appropriately if this Mercurial is not installed system-wide.
172 set appropriately if this Mercurial is not installed system-wide.
173 ''')),
173 ''')),
174
174
175 (['revs', 'revisions'], _('Specifying Single Revisions'),
175 (['revs', 'revisions'], _('Specifying Single Revisions'),
176 _(r'''
176 _(r'''
177 Mercurial supports several ways to specify individual revisions.
177 Mercurial supports several ways to specify individual revisions.
178
178
179 A plain integer is treated as a revision number. Negative integers
179 A plain integer is treated as a revision number. Negative integers
180 are treated as topological offsets from the tip, with -1 denoting
180 are treated as topological offsets from the tip, with -1 denoting
181 the tip. As such, negative numbers are only useful if you've
181 the tip. As such, negative numbers are only useful if you've
182 memorized your local tree numbers and want to save typing a single
182 memorized your local tree numbers and want to save typing a single
183 digit. This editor suggests copy and paste.
183 digit. This editor suggests copy and paste.
184
184
185 A 40-digit hexadecimal string is treated as a unique revision
185 A 40-digit hexadecimal string is treated as a unique revision
186 identifier.
186 identifier.
187
187
188 A hexadecimal string less than 40 characters long is treated as a
188 A hexadecimal string less than 40 characters long is treated as a
189 unique revision identifier, and referred to as a short-form
189 unique revision identifier, and referred to as a short-form
190 identifier. A short-form identifier is only valid if it is the
190 identifier. A short-form identifier is only valid if it is the
191 prefix of exactly one full-length identifier.
191 prefix of exactly one full-length identifier.
192
192
193 Any other string is treated as a tag name, which is a symbolic
193 Any other string is treated as a tag name, which is a symbolic
194 name associated with a revision identifier. Tag names may not
194 name associated with a revision identifier. Tag names may not
195 contain the ":" character.
195 contain the ":" character.
196
196
197 The reserved name "tip" is a special tag that always identifies
197 The reserved name "tip" is a special tag that always identifies
198 the most recent revision.
198 the most recent revision.
199
199
200 The reserved name "null" indicates the null revision. This is the
200 The reserved name "null" indicates the null revision. This is the
201 revision of an empty repository, and the parent of revision 0.
201 revision of an empty repository, and the parent of revision 0.
202
202
203 The reserved name "." indicates the working directory parent. If
203 The reserved name "." indicates the working directory parent. If
204 no working directory is checked out, it is equivalent to null. If
204 no working directory is checked out, it is equivalent to null. If
205 an uncommitted merge is in progress, "." is the revision of the
205 an uncommitted merge is in progress, "." is the revision of the
206 first parent.
206 first parent.
207 ''')),
207 ''')),
208
208
209 (['mrevs', 'multirevs'], _('Specifying Multiple Revisions'),
209 (['mrevs', 'multirevs'], _('Specifying Multiple Revisions'),
210 _(r'''
210 _(r'''
211 When Mercurial accepts more than one revision, they may be
211 When Mercurial accepts more than one revision, they may be
212 specified individually, or provided as a topologically continuous
212 specified individually, or provided as a topologically continuous
213 range, separated by the ":" character.
213 range, separated by the ":" character.
214
214
215 The syntax of range notation is [BEGIN]:[END], where BEGIN and END
215 The syntax of range notation is [BEGIN]:[END], where BEGIN and END
216 are revision identifiers. Both BEGIN and END are optional. If
216 are revision identifiers. Both BEGIN and END are optional. If
217 BEGIN is not specified, it defaults to revision number 0. If END
217 BEGIN is not specified, it defaults to revision number 0. If END
218 is not specified, it defaults to the tip. The range ":" thus means
218 is not specified, it defaults to the tip. The range ":" thus means
219 "all revisions".
219 "all revisions".
220
220
221 If BEGIN is greater than END, revisions are treated in reverse
221 If BEGIN is greater than END, revisions are treated in reverse
222 order.
222 order.
223
223
224 A range acts as a closed interval. This means that a range of 3:5
224 A range acts as a closed interval. This means that a range of 3:5
225 gives 3, 4 and 5. Similarly, a range of 9:6 gives 9, 8, 7, and 6.
225 gives 3, 4 and 5. Similarly, a range of 9:6 gives 9, 8, 7, and 6.
226 ''')),
226 ''')),
227
227
228 (['diffs'], _('Diff Formats'),
228 (['diffs'], _('Diff Formats'),
229 _(r'''
229 _(r'''
230 Mercurial's default format for showing changes between two
230 Mercurial's default format for showing changes between two
231 versions of a file is compatible with the unified format of GNU
231 versions of a file is compatible with the unified format of GNU
232 diff, which can be used by GNU patch and many other standard
232 diff, which can be used by GNU patch and many other standard
233 tools.
233 tools.
234
234
235 While this standard format is often enough, it does not encode the
235 While this standard format is often enough, it does not encode the
236 following information:
236 following information:
237
237
238 - executable status and other permission bits
238 - executable status and other permission bits
239 - copy or rename information
239 - copy or rename information
240 - changes in binary files
240 - changes in binary files
241 - creation or deletion of empty files
241 - creation or deletion of empty files
242
242
243 Mercurial also supports the extended diff format from the git VCS
243 Mercurial also supports the extended diff format from the git VCS
244 which addresses these limitations. The git diff format is not
244 which addresses these limitations. The git diff format is not
245 produced by default because a few widespread tools still do not
245 produced by default because a few widespread tools still do not
246 understand this format.
246 understand this format.
247
247
248 This means that when generating diffs from a Mercurial repository
248 This means that when generating diffs from a Mercurial repository
249 (e.g. with "hg export"), you should be careful about things like
249 (e.g. with "hg export"), you should be careful about things like
250 file copies and renames or other things mentioned above, because
250 file copies and renames or other things mentioned above, because
251 when applying a standard diff to a different repository, this
251 when applying a standard diff to a different repository, this
252 extra information is lost. Mercurial's internal operations (like
252 extra information is lost. Mercurial's internal operations (like
253 push and pull) are not affected by this, because they use an
253 push and pull) are not affected by this, because they use an
254 internal binary format for communicating changes.
254 internal binary format for communicating changes.
255
255
256 To make Mercurial produce the git extended diff format, use the
256 To make Mercurial produce the git extended diff format, use the
257 --git option available for many commands, or set 'git = True' in
257 --git option available for many commands, or set 'git = True' in
258 the [diff] section of your hgrc. You do not need to set this
258 the [diff] section of your hgrc. You do not need to set this
259 option when importing diffs in this format or using them in the mq
259 option when importing diffs in this format or using them in the mq
260 extension.
260 extension.
261 ''')),
261 ''')),
262 (['templating'], _('Template Usage'),
262 (['templating'], _('Template Usage'),
263 _(r'''
263 _(r'''
264 Mercurial allows you to customize output of commands through
264 Mercurial allows you to customize output of commands through
265 templates. You can either pass in a template from the command
265 templates. You can either pass in a template from the command
266 line, via the --template option, or select an existing
266 line, via the --template option, or select an existing
267 template-style (--style).
267 template-style (--style).
268
268
269 You can customize output for any "log-like" command: log,
269 You can customize output for any "log-like" command: log,
270 outgoing, incoming, tip, parents, heads and glog.
270 outgoing, incoming, tip, parents, heads and glog.
271
271
272 Three styles are packaged with Mercurial: default (the style used
272 Three styles are packaged with Mercurial: default (the style used
273 when no explicit preference is passed), compact and changelog.
273 when no explicit preference is passed), compact and changelog.
274 Usage:
274 Usage:
275
275
276 $ hg log -r1 --style changelog
276 $ hg log -r1 --style changelog
277
277
278 A template is a piece of text, with markup to invoke variable
278 A template is a piece of text, with markup to invoke variable
279 expansion:
279 expansion:
280
280
281 $ hg log -r1 --template "{node}\n"
281 $ hg log -r1 --template "{node}\n"
282 b56ce7b07c52de7d5fd79fb89701ea538af65746
282 b56ce7b07c52de7d5fd79fb89701ea538af65746
283
283
284 Strings in curly braces are called keywords. The availability of
284 Strings in curly braces are called keywords. The availability of
285 keywords depends on the exact context of the templater. These
285 keywords depends on the exact context of the templater. These
286 keywords are usually available for templating a log-like command:
286 keywords are usually available for templating a log-like command:
287
287
288 - author: String. The unmodified author of the changeset.
288 - author: String. The unmodified author of the changeset.
289 - branches: String. The name of the branch on which the changeset
289 - branches: String. The name of the branch on which the changeset
290 was committed. Will be empty if the branch name was default.
290 was committed. Will be empty if the branch name was default.
291 - date: Date information. The date when the changeset was committed.
291 - date: Date information. The date when the changeset was committed.
292 - desc: String. The text of the changeset description.
292 - desc: String. The text of the changeset description.
293 - diffstat: String. Statistics of changes with the following
293 - diffstat: String. Statistics of changes with the following
294 format: "modified files: +added/-removed lines"
294 format: "modified files: +added/-removed lines"
295 - files: List of strings. All files modified, added, or removed by
295 - files: List of strings. All files modified, added, or removed by
296 this changeset.
296 this changeset.
297 - file_adds: List of strings. Files added by this changeset.
297 - file_adds: List of strings. Files added by this changeset.
298 - file_mods: List of strings. Files modified by this changeset.
298 - file_mods: List of strings. Files modified by this changeset.
299 - file_dels: List of strings. Files removed by this changeset.
299 - file_dels: List of strings. Files removed by this changeset.
300 - node: String. The changeset identification hash, as a
300 - node: String. The changeset identification hash, as a
301 40-character hexadecimal string.
301 40-character hexadecimal string.
302 - parents: List of strings. The parents of the changeset.
302 - parents: List of strings. The parents of the changeset.
303 - rev: Integer. The repository-local changeset revision number.
303 - rev: Integer. The repository-local changeset revision number.
304 - tags: List of strings. Any tags associated with the changeset.
304 - tags: List of strings. Any tags associated with the changeset.
305
305
306 The "date" keyword does not produce human-readable output. If you
306 The "date" keyword does not produce human-readable output. If you
307 want to use a date in your output, you can use a filter to process
307 want to use a date in your output, you can use a filter to process
308 it. Filters are functions which return a string based on the input
308 it. Filters are functions which return a string based on the input
309 variable. You can also use a chain of filters to get the desired
309 variable. You can also use a chain of filters to get the desired
310 output:
310 output:
311
311
312 $ hg tip --template "{date|isodate}\n"
312 $ hg tip --template "{date|isodate}\n"
313 2008-08-21 18:22 +0000
313 2008-08-21 18:22 +0000
314
314
315 List of filters:
315 List of filters:
316
316
317 - addbreaks: Any text. Add an XHTML "<br />" tag before the end of
317 - addbreaks: Any text. Add an XHTML "<br />" tag before the end of
318 every line except the last.
318 every line except the last.
319 - age: Date. Returns a human-readable date/time difference between
319 - age: Date. Returns a human-readable date/time difference between
320 the given date/time and the current date/time.
320 the given date/time and the current date/time.
321 - basename: Any text. Treats the text as a path, and returns the
321 - basename: Any text. Treats the text as a path, and returns the
322 last component of the path after splitting by the path
322 last component of the path after splitting by the path
323 separator (ignoring trailing seprators). For example,
323 separator (ignoring trailing separators). For example,
324 "foo/bar/baz" becomes "baz" and "foo/bar//" becomes "bar".
324 "foo/bar/baz" becomes "baz" and "foo/bar//" becomes "bar".
325 - stripdir: Treat the text as path and strip a directory level, if
325 - stripdir: Treat the text as path and strip a directory level, if
326 possible. For example, "foo" and "foo/bar" becomes "foo".
326 possible. For example, "foo" and "foo/bar" becomes "foo".
327 - date: Date. Returns a date in a Unix date format, including
327 - date: Date. Returns a date in a Unix date format, including
328 the timezone: "Mon Sep 04 15:13:13 2006 0700".
328 the timezone: "Mon Sep 04 15:13:13 2006 0700".
329 - domain: Any text. Finds the first string that looks like an
329 - domain: Any text. Finds the first string that looks like an
330 email address, and extracts just the domain component.
330 email address, and extracts just the domain component.
331 Example: 'User <user@example.com>' becomes 'example.com'.
331 Example: 'User <user@example.com>' becomes 'example.com'.
332 - email: Any text. Extracts the first string that looks like an
332 - email: Any text. Extracts the first string that looks like an
333 email address. Example: 'User <user@example.com>' becomes
333 email address. Example: 'User <user@example.com>' becomes
334 'user@example.com'.
334 'user@example.com'.
335 - escape: Any text. Replaces the special XML/XHTML characters "&",
335 - escape: Any text. Replaces the special XML/XHTML characters "&",
336 "<" and ">" with XML entities.
336 "<" and ">" with XML entities.
337 - fill68: Any text. Wraps the text to fit in 68 columns.
337 - fill68: Any text. Wraps the text to fit in 68 columns.
338 - fill76: Any text. Wraps the text to fit in 76 columns.
338 - fill76: Any text. Wraps the text to fit in 76 columns.
339 - firstline: Any text. Returns the first line of text.
339 - firstline: Any text. Returns the first line of text.
340 - nonempty: Any text. Returns '(none)' if the string is empty.
340 - nonempty: Any text. Returns '(none)' if the string is empty.
341 - hgdate: Date. Returns the date as a pair of numbers:
341 - hgdate: Date. Returns the date as a pair of numbers:
342 "1157407993 25200" (Unix timestamp, timezone offset).
342 "1157407993 25200" (Unix timestamp, timezone offset).
343 - isodate: Date. Returns the date in ISO 8601 format.
343 - isodate: Date. Returns the date in ISO 8601 format.
344 - localdate: Date. Converts a date to local date.
344 - localdate: Date. Converts a date to local date.
345 - obfuscate: Any text. Returns the input text rendered as a
345 - obfuscate: Any text. Returns the input text rendered as a
346 sequence of XML entities.
346 sequence of XML entities.
347 - person: Any text. Returns the text before an email address.
347 - person: Any text. Returns the text before an email address.
348 - rfc822date: Date. Returns a date using the same format used
348 - rfc822date: Date. Returns a date using the same format used
349 in email headers.
349 in email headers.
350 - short: Changeset hash. Returns the short form of a changeset
350 - short: Changeset hash. Returns the short form of a changeset
351 hash, i.e. a 12-byte hexadecimal string.
351 hash, i.e. a 12-byte hexadecimal string.
352 - shortdate: Date. Returns a date like "2006-09-18".
352 - shortdate: Date. Returns a date like "2006-09-18".
353 - strip: Any text. Strips all leading and trailing whitespace.
353 - strip: Any text. Strips all leading and trailing whitespace.
354 - tabindent: Any text. Returns the text, with every line except
354 - tabindent: Any text. Returns the text, with every line except
355 the first starting with a tab character.
355 the first starting with a tab character.
356 - urlescape: Any text. Escapes all "special" characters. For
356 - urlescape: Any text. Escapes all "special" characters. For
357 example, "foo bar" becomes "foo%20bar".
357 example, "foo bar" becomes "foo%20bar".
358 - user: Any text. Returns the user portion of an email address.
358 - user: Any text. Returns the user portion of an email address.
359 ''')),
359 ''')),
360
360
361 (['urls'], _('URL Paths'),
361 (['urls'], _('URL Paths'),
362 _(r'''
362 _(r'''
363 Valid URLs are of the form:
363 Valid URLs are of the form:
364
364
365 local/filesystem/path (or file://local/filesystem/path)
365 local/filesystem/path (or file://local/filesystem/path)
366 http://[user[:pass]@]host[:port]/[path]
366 http://[user[:pass]@]host[:port]/[path]
367 https://[user[:pass]@]host[:port]/[path]
367 https://[user[:pass]@]host[:port]/[path]
368 ssh://[user[:pass]@]host[:port]/[path]
368 ssh://[user[:pass]@]host[:port]/[path]
369
369
370 Paths in the local filesystem can either point to Mercurial
370 Paths in the local filesystem can either point to Mercurial
371 repositories or to bundle files (as created by 'hg bundle' or
371 repositories or to bundle files (as created by 'hg bundle' or
372 'hg incoming --bundle').
372 'hg incoming --bundle').
373
373
374 An optional identifier after # indicates a particular branch, tag,
374 An optional identifier after # indicates a particular branch, tag,
375 or changeset to use from the remote repository.
375 or changeset to use from the remote repository.
376
376
377 Some features, such as pushing to http:// and https:// URLs are
377 Some features, such as pushing to http:// and https:// URLs are
378 only possible if the feature is explicitly enabled on the remote
378 only possible if the feature is explicitly enabled on the remote
379 Mercurial server.
379 Mercurial server.
380
380
381 Some notes about using SSH with Mercurial:
381 Some notes about using SSH with Mercurial:
382 - SSH requires an accessible shell account on the destination
382 - SSH requires an accessible shell account on the destination
383 machine and a copy of hg in the remote path or specified with as
383 machine and a copy of hg in the remote path or specified with as
384 remotecmd.
384 remotecmd.
385 - path is relative to the remote user's home directory by default.
385 - path is relative to the remote user's home directory by default.
386 Use an extra slash at the start of a path to specify an absolute path:
386 Use an extra slash at the start of a path to specify an absolute path:
387 ssh://example.com//tmp/repository
387 ssh://example.com//tmp/repository
388 - Mercurial doesn't use its own compression via SSH; the right
388 - Mercurial doesn't use its own compression via SSH; the right
389 thing to do is to configure it in your ~/.ssh/config, e.g.:
389 thing to do is to configure it in your ~/.ssh/config, e.g.:
390 Host *.mylocalnetwork.example.com
390 Host *.mylocalnetwork.example.com
391 Compression no
391 Compression no
392 Host *
392 Host *
393 Compression yes
393 Compression yes
394 Alternatively specify "ssh -C" as your ssh command in your hgrc
394 Alternatively specify "ssh -C" as your ssh command in your hgrc
395 or with the --ssh command line option.
395 or with the --ssh command line option.
396
396
397 These URLs can all be stored in your hgrc with path aliases under
397 These URLs can all be stored in your hgrc with path aliases under
398 the [paths] section like so:
398 the [paths] section like so:
399 [paths]
399 [paths]
400 alias1 = URL1
400 alias1 = URL1
401 alias2 = URL2
401 alias2 = URL2
402 ...
402 ...
403
403
404 You can then use the alias for any command that uses a URL (for
404 You can then use the alias for any command that uses a URL (for
405 example 'hg pull alias1' would pull from the 'alias1' path).
405 example 'hg pull alias1' would pull from the 'alias1' path).
406
406
407 Two path aliases are special because they are used as defaults
407 Two path aliases are special because they are used as defaults
408 when you do not provide the URL to a command:
408 when you do not provide the URL to a command:
409
409
410 default:
410 default:
411 When you create a repository with hg clone, the clone command
411 When you create a repository with hg clone, the clone command
412 saves the location of the source repository as the new
412 saves the location of the source repository as the new
413 repository's 'default' path. This is then used when you omit
413 repository's 'default' path. This is then used when you omit
414 path from push- and pull-like commands (including incoming and
414 path from push- and pull-like commands (including incoming and
415 outgoing).
415 outgoing).
416
416
417 default-push:
417 default-push:
418 The push command will look for a path named 'default-push', and
418 The push command will look for a path named 'default-push', and
419 prefer it over 'default' if both are defined.
419 prefer it over 'default' if both are defined.
420 ''')),
420 ''')),
421 )
421 )
@@ -1,261 +1,261 b''
1 hg convert [OPTION]... SOURCE [DEST [REVMAP]]
1 hg convert [OPTION]... SOURCE [DEST [REVMAP]]
2
2
3 convert a foreign SCM repository to a Mercurial one.
3 convert a foreign SCM repository to a Mercurial one.
4
4
5 Accepted source formats [identifiers]:
5 Accepted source formats [identifiers]:
6 - Mercurial [hg]
6 - Mercurial [hg]
7 - CVS [cvs]
7 - CVS [cvs]
8 - Darcs [darcs]
8 - Darcs [darcs]
9 - git [git]
9 - git [git]
10 - Subversion [svn]
10 - Subversion [svn]
11 - Monotone [mtn]
11 - Monotone [mtn]
12 - GNU Arch [gnuarch]
12 - GNU Arch [gnuarch]
13 - Bazaar [bzr]
13 - Bazaar [bzr]
14 - Perforce [p4]
14 - Perforce [p4]
15
15
16 Accepted destination formats [identifiers]:
16 Accepted destination formats [identifiers]:
17 - Mercurial [hg]
17 - Mercurial [hg]
18 - Subversion [svn] (history on branches is not preserved)
18 - Subversion [svn] (history on branches is not preserved)
19
19
20 If no revision is given, all revisions will be converted.
20 If no revision is given, all revisions will be converted.
21 Otherwise, convert will only import up to the named revision
21 Otherwise, convert will only import up to the named revision
22 (given in a format understood by the source).
22 (given in a format understood by the source).
23
23
24 If no destination directory name is specified, it defaults to the
24 If no destination directory name is specified, it defaults to the
25 basename of the source with '-hg' appended. If the destination
25 basename of the source with '-hg' appended. If the destination
26 repository doesn't exist, it will be created.
26 repository doesn't exist, it will be created.
27
27
28 If <REVMAP> isn't given, it will be put in a default location
28 If <REVMAP> isn't given, it will be put in a default location
29 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file
29 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file
30 that maps each source commit ID to the destination ID for that
30 that maps each source commit ID to the destination ID for that
31 revision, like so:
31 revision, like so:
32 <source ID> <destination ID>
32 <source ID> <destination ID>
33
33
34 If the file doesn't exist, it's automatically created. It's
34 If the file doesn't exist, it's automatically created. It's
35 updated on each commit copied, so convert-repo can be interrupted
35 updated on each commit copied, so convert-repo can be interrupted
36 and can be run repeatedly to copy new commits.
36 and can be run repeatedly to copy new commits.
37
37
38 The [username mapping] file is a simple text file that maps each
38 The [username mapping] file is a simple text file that maps each
39 source commit author to a destination commit author. It is handy
39 source commit author to a destination commit author. It is handy
40 for source SCMs that use unix logins to identify authors (eg:
40 for source SCMs that use unix logins to identify authors (eg:
41 CVS). One line per author mapping and the line format is:
41 CVS). One line per author mapping and the line format is:
42 srcauthor=whatever string you want
42 srcauthor=whatever string you want
43
43
44 The filemap is a file that allows filtering and remapping of files
44 The filemap is a file that allows filtering and remapping of files
45 and directories. Comment lines start with '#'. Each line can
45 and directories. Comment lines start with '#'. Each line can
46 contain one of the following directives:
46 contain one of the following directives:
47
47
48 include path/to/file
48 include path/to/file
49
49
50 exclude path/to/file
50 exclude path/to/file
51
51
52 rename from/file to/file
52 rename from/file to/file
53
53
54 The 'include' directive causes a file, or all files under a
54 The 'include' directive causes a file, or all files under a
55 directory, to be included in the destination repository, and the
55 directory, to be included in the destination repository, and the
56 exclusion of all other files and directories not explicitely included.
56 exclusion of all other files and directories not explicitly included.
57 The 'exclude' directive causes files or directories to be omitted.
57 The 'exclude' directive causes files or directories to be omitted.
58 The 'rename' directive renames a file or directory. To rename from
58 The 'rename' directive renames a file or directory. To rename from
59 a subdirectory into the root of the repository, use '.' as the
59 a subdirectory into the root of the repository, use '.' as the
60 path to rename to.
60 path to rename to.
61
61
62 The splicemap is a file that allows insertion of synthetic
62 The splicemap is a file that allows insertion of synthetic
63 history, letting you specify the parents of a revision. This is
63 history, letting you specify the parents of a revision. This is
64 useful if you want to e.g. give a Subversion merge two parents, or
64 useful if you want to e.g. give a Subversion merge two parents, or
65 graft two disconnected series of history together. Each entry
65 graft two disconnected series of history together. Each entry
66 contains a key, followed by a space, followed by one or two
66 contains a key, followed by a space, followed by one or two
67 comma-separated values. The key is the revision ID in the source
67 comma-separated values. The key is the revision ID in the source
68 revision control system whose parents should be modified (same
68 revision control system whose parents should be modified (same
69 format as a key in .hg/shamap). The values are the revision IDs
69 format as a key in .hg/shamap). The values are the revision IDs
70 (in either the source or destination revision control system) that
70 (in either the source or destination revision control system) that
71 should be used as the new parents for that node.
71 should be used as the new parents for that node.
72
72
73 The branchmap is a file that allows you to rename a branch when it is
73 The branchmap is a file that allows you to rename a branch when it is
74 being brought in from whatever external repository. When used in
74 being brought in from whatever external repository. When used in
75 conjunction with a splicemap, it allows for a powerful combination
75 conjunction with a splicemap, it allows for a powerful combination
76 to help fix even the most badly mismanaged repositories and turn them
76 to help fix even the most badly mismanaged repositories and turn them
77 into nicely structured Mercurial repositories. The branchmap contains
77 into nicely structured Mercurial repositories. The branchmap contains
78 lines of the form "original_branch_name new_branch_name".
78 lines of the form "original_branch_name new_branch_name".
79 "original_branch_name" is the name of the branch in the source
79 "original_branch_name" is the name of the branch in the source
80 repository, and "new_branch_name" is the name of the branch is the
80 repository, and "new_branch_name" is the name of the branch is the
81 destination repository. This can be used to (for instance) move code
81 destination repository. This can be used to (for instance) move code
82 in one repository from "default" to a named branch.
82 in one repository from "default" to a named branch.
83
83
84 Mercurial Source
84 Mercurial Source
85 -----------------
85 -----------------
86
86
87 --config convert.hg.ignoreerrors=False (boolean)
87 --config convert.hg.ignoreerrors=False (boolean)
88 ignore integrity errors when reading. Use it to fix Mercurial
88 ignore integrity errors when reading. Use it to fix Mercurial
89 repositories with missing revlogs, by converting from and to
89 repositories with missing revlogs, by converting from and to
90 Mercurial.
90 Mercurial.
91 --config convert.hg.saverev=False (boolean)
91 --config convert.hg.saverev=False (boolean)
92 store original revision ID in changeset (forces target IDs to
92 store original revision ID in changeset (forces target IDs to
93 change)
93 change)
94 --config convert.hg.startrev=0 (hg revision identifier)
94 --config convert.hg.startrev=0 (hg revision identifier)
95 convert start revision and its descendants
95 convert start revision and its descendants
96
96
97 CVS Source
97 CVS Source
98 ----------
98 ----------
99
99
100 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
100 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
101 to indicate the starting point of what will be converted. Direct
101 to indicate the starting point of what will be converted. Direct
102 access to the repository files is not needed, unless of course the
102 access to the repository files is not needed, unless of course the
103 repository is :local:. The conversion uses the top level directory
103 repository is :local:. The conversion uses the top level directory
104 in the sandbox to find the CVS repository, and then uses CVS rlog
104 in the sandbox to find the CVS repository, and then uses CVS rlog
105 commands to find files to convert. This means that unless a
105 commands to find files to convert. This means that unless a
106 filemap is given, all files under the starting directory will be
106 filemap is given, all files under the starting directory will be
107 converted, and that any directory reorganisation in the CVS
107 converted, and that any directory reorganization in the CVS
108 sandbox is ignored.
108 sandbox is ignored.
109
109
110 Because CVS does not have changesets, it is necessary to collect
110 Because CVS does not have changesets, it is necessary to collect
111 individual commits to CVS and merge them into changesets. CVS
111 individual commits to CVS and merge them into changesets. CVS
112 source uses its internal changeset merging code by default but can
112 source uses its internal changeset merging code by default but can
113 be configured to call the external 'cvsps' program by setting:
113 be configured to call the external 'cvsps' program by setting:
114 --config convert.cvsps='cvsps -A -u --cvs-direct -q'
114 --config convert.cvsps='cvsps -A -u --cvs-direct -q'
115 This option is deprecated and will be removed in Mercurial 1.4.
115 This option is deprecated and will be removed in Mercurial 1.4.
116
116
117 The options shown are the defaults.
117 The options shown are the defaults.
118
118
119 Internal cvsps is selected by setting
119 Internal cvsps is selected by setting
120 --config convert.cvsps=builtin
120 --config convert.cvsps=builtin
121 and has a few more configurable options:
121 and has a few more configurable options:
122 --config convert.cvsps.cache=True (boolean)
122 --config convert.cvsps.cache=True (boolean)
123 Set to False to disable remote log caching, for testing and
123 Set to False to disable remote log caching, for testing and
124 debugging purposes.
124 debugging purposes.
125 --config convert.cvsps.fuzz=60 (integer)
125 --config convert.cvsps.fuzz=60 (integer)
126 Specify the maximum time (in seconds) that is allowed
126 Specify the maximum time (in seconds) that is allowed
127 between commits with identical user and log message in a
127 between commits with identical user and log message in a
128 single changeset. When very large files were checked in as
128 single changeset. When very large files were checked in as
129 part of a changeset then the default may not be long
129 part of a changeset then the default may not be long
130 enough.
130 enough.
131 --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
131 --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
132 Specify a regular expression to which commit log messages
132 Specify a regular expression to which commit log messages
133 are matched. If a match occurs, then the conversion
133 are matched. If a match occurs, then the conversion
134 process will insert a dummy revision merging the branch on
134 process will insert a dummy revision merging the branch on
135 which this log message occurs to the branch indicated in
135 which this log message occurs to the branch indicated in
136 the regex.
136 the regex.
137 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
137 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
138 Specify a regular expression to which commit log messages
138 Specify a regular expression to which commit log messages
139 are matched. If a match occurs, then the conversion
139 are matched. If a match occurs, then the conversion
140 process will add the most recent revision on the branch
140 process will add the most recent revision on the branch
141 indicated in the regex as the second parent of the
141 indicated in the regex as the second parent of the
142 changeset.
142 changeset.
143
143
144 The hgext/convert/cvsps wrapper script allows the builtin
144 The hgext/convert/cvsps wrapper script allows the builtin
145 changeset merging code to be run without doing a conversion. Its
145 changeset merging code to be run without doing a conversion. Its
146 parameters and output are similar to that of cvsps 2.1.
146 parameters and output are similar to that of cvsps 2.1.
147
147
148 Subversion Source
148 Subversion Source
149 -----------------
149 -----------------
150
150
151 Subversion source detects classical trunk/branches/tags layouts.
151 Subversion source detects classical trunk/branches/tags layouts.
152 By default, the supplied "svn://repo/path/" source URL is
152 By default, the supplied "svn://repo/path/" source URL is
153 converted as a single branch. If "svn://repo/path/trunk" exists it
153 converted as a single branch. If "svn://repo/path/trunk" exists it
154 replaces the default branch. If "svn://repo/path/branches" exists,
154 replaces the default branch. If "svn://repo/path/branches" exists,
155 its subdirectories are listed as possible branches. If
155 its subdirectories are listed as possible branches. If
156 "svn://repo/path/tags" exists, it is looked for tags referencing
156 "svn://repo/path/tags" exists, it is looked for tags referencing
157 converted branches. Default "trunk", "branches" and "tags" values
157 converted branches. Default "trunk", "branches" and "tags" values
158 can be overriden with following options. Set them to paths
158 can be overridden with following options. Set them to paths
159 relative to the source URL, or leave them blank to disable
159 relative to the source URL, or leave them blank to disable auto
160 autodetection.
160 detection.
161
161
162 --config convert.svn.branches=branches (directory name)
162 --config convert.svn.branches=branches (directory name)
163 specify the directory containing branches
163 specify the directory containing branches
164 --config convert.svn.tags=tags (directory name)
164 --config convert.svn.tags=tags (directory name)
165 specify the directory containing tags
165 specify the directory containing tags
166 --config convert.svn.trunk=trunk (directory name)
166 --config convert.svn.trunk=trunk (directory name)
167 specify the name of the trunk branch
167 specify the name of the trunk branch
168
168
169 Source history can be retrieved starting at a specific revision,
169 Source history can be retrieved starting at a specific revision,
170 instead of being integrally converted. Only single branch
170 instead of being integrally converted. Only single branch
171 conversions are supported.
171 conversions are supported.
172
172
173 --config convert.svn.startrev=0 (svn revision number)
173 --config convert.svn.startrev=0 (svn revision number)
174 specify start Subversion revision.
174 specify start Subversion revision.
175
175
176 Perforce Source
176 Perforce Source
177 ---------------
177 ---------------
178
178
179 The Perforce (P4) importer can be given a p4 depot path or a
179 The Perforce (P4) importer can be given a p4 depot path or a
180 client specification as source. It will convert all files in the
180 client specification as source. It will convert all files in the
181 source to a flat Mercurial repository, ignoring labels, branches
181 source to a flat Mercurial repository, ignoring labels, branches
182 and integrations. Note that when a depot path is given you then
182 and integrations. Note that when a depot path is given you then
183 usually should specify a target directory, because otherwise the
183 usually should specify a target directory, because otherwise the
184 target may be named ...-hg.
184 target may be named ...-hg.
185
185
186 It is possible to limit the amount of source history to be
186 It is possible to limit the amount of source history to be
187 converted by specifying an initial Perforce revision.
187 converted by specifying an initial Perforce revision.
188
188
189 --config convert.p4.startrev=0 (perforce changelist number)
189 --config convert.p4.startrev=0 (perforce changelist number)
190 specify initial Perforce revision.
190 specify initial Perforce revision.
191
191
192
192
193 Mercurial Destination
193 Mercurial Destination
194 ---------------------
194 ---------------------
195
195
196 --config convert.hg.clonebranches=False (boolean)
196 --config convert.hg.clonebranches=False (boolean)
197 dispatch source branches in separate clones.
197 dispatch source branches in separate clones.
198 --config convert.hg.tagsbranch=default (branch name)
198 --config convert.hg.tagsbranch=default (branch name)
199 tag revisions branch name
199 tag revisions branch name
200 --config convert.hg.usebranchnames=True (boolean)
200 --config convert.hg.usebranchnames=True (boolean)
201 preserve branch names
201 preserve branch names
202
202
203 options:
203 options:
204
204
205 -A --authors username mapping filename
205 -A --authors username mapping filename
206 -d --dest-type destination repository type
206 -d --dest-type destination repository type
207 --filemap remap file names using contents of file
207 --filemap remap file names using contents of file
208 -r --rev import up to target revision REV
208 -r --rev import up to target revision REV
209 -s --source-type source repository type
209 -s --source-type source repository type
210 --splicemap splice synthesized history into place
210 --splicemap splice synthesized history into place
211 --branchmap change branch names while converting
211 --branchmap change branch names while converting
212 --datesort try to sort changesets by date
212 --datesort try to sort changesets by date
213
213
214 use "hg -v help convert" to show global options
214 use "hg -v help convert" to show global options
215 adding a
215 adding a
216 assuming destination a-hg
216 assuming destination a-hg
217 initializing destination a-hg repository
217 initializing destination a-hg repository
218 scanning source...
218 scanning source...
219 sorting...
219 sorting...
220 converting...
220 converting...
221 4 a
221 4 a
222 3 b
222 3 b
223 2 c
223 2 c
224 1 d
224 1 d
225 0 e
225 0 e
226 pulling from ../a
226 pulling from ../a
227 searching for changes
227 searching for changes
228 no changes found
228 no changes found
229 % should fail
229 % should fail
230 initializing destination bogusfile repository
230 initializing destination bogusfile repository
231 abort: cannot create new bundle repository
231 abort: cannot create new bundle repository
232 % should fail
232 % should fail
233 abort: Permission denied: bogusdir
233 abort: Permission denied: bogusdir
234 % should succeed
234 % should succeed
235 initializing destination bogusdir repository
235 initializing destination bogusdir repository
236 scanning source...
236 scanning source...
237 sorting...
237 sorting...
238 converting...
238 converting...
239 4 a
239 4 a
240 3 b
240 3 b
241 2 c
241 2 c
242 1 d
242 1 d
243 0 e
243 0 e
244 % test pre and post conversion actions
244 % test pre and post conversion actions
245 run hg source pre-conversion action
245 run hg source pre-conversion action
246 run hg sink pre-conversion action
246 run hg sink pre-conversion action
247 run hg sink post-conversion action
247 run hg sink post-conversion action
248 run hg source post-conversion action
248 run hg source post-conversion action
249 % converting empty dir should fail nicely
249 % converting empty dir should fail nicely
250 assuming destination emptydir-hg
250 assuming destination emptydir-hg
251 initializing destination emptydir-hg repository
251 initializing destination emptydir-hg repository
252 emptydir does not look like a CVS checkout
252 emptydir does not look like a CVS checkout
253 emptydir does not look like a Git repo
253 emptydir does not look like a Git repo
254 emptydir does not look like a Subversion repo
254 emptydir does not look like a Subversion repo
255 emptydir is not a local Mercurial repo
255 emptydir is not a local Mercurial repo
256 emptydir does not look like a darcs repo
256 emptydir does not look like a darcs repo
257 emptydir does not look like a monotone repo
257 emptydir does not look like a monotone repo
258 emptydir does not look like a GNU Arch repo
258 emptydir does not look like a GNU Arch repo
259 emptydir does not look like a Bazaar repo
259 emptydir does not look like a Bazaar repo
260 emptydir does not look like a P4 repo
260 emptydir does not look like a P4 repo
261 abort: emptydir: missing or unsupported repository
261 abort: emptydir: missing or unsupported repository
@@ -1,502 +1,502 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$ in
4 This extension expands RCS/CVS-like or self-customized $Keywords$ in
5 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 the
7 Keywords are only expanded in local repositories and not stored in the
8 change history. The mechanism can be regarded as a convenience for the
8 change history. The mechanism can be regarded as a convenience for the
9 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 of
11 Configuration is done in the [keyword] and [keywordmaps] sections of
12 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 repositories.
22 the less you lose speed in huge repositories.
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
29 The default template mappings (view with "hg kwdemo -d") can be
30 replaced with customized keywords and templates. Again, run "hg
30 replaced with customized keywords and templates. Again, run "hg
31 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
34 the risk of inadvertently storing expanded keywords in the change
35 history.
35 history.
36
36
37 To force expansion after enabling it, or a configuration change, run
37 To force expansion after enabling it, or a configuration change, run
38 "hg kwexpand".
38 "hg kwexpand".
39
39
40 Also, when committing with the record extension or using mq's qrecord,
40 Also, when committing with the record extension or using mq's qrecord,
41 be aware that keywords cannot be updated. Again, run "hg kwexpand" on
41 be aware that keywords cannot be updated. Again, run "hg kwexpand" on
42 the files in question to update keyword expansions after all changes
42 the files in question to update keyword expansions after all changes
43 have been checked in.
43 have been checked in.
44
44
45 Expansions spanning more than one line and incremental expansions,
45 Expansions spanning more than one line and incremental expansions,
46 like CVS' $Log$, are not supported. A keyword template map
46 like CVS' $Log$, are not supported. A keyword template map
47 "Log = {desc}" expands to the first line of the changeset description.
47 "Log = {desc}" expands to the first line of the changeset description.
48
48
49 list of commands:
49 list of commands:
50
50
51 kwdemo print [keywordmaps] configuration and an expansion example
51 kwdemo print [keywordmaps] configuration and an expansion example
52 kwexpand expand keywords in working directory
52 kwexpand expand keywords in working directory
53 kwfiles print files currently configured for keyword expansion
53 kwfiles print files currently configured for keyword expansion
54 kwshrink revert expanded keywords in working directory
54 kwshrink revert expanded keywords in working directory
55
55
56 enabled extensions:
56 enabled extensions:
57
57
58 keyword keyword expansion in local repositories
58 keyword keyword expansion in local repositories
59 mq patch management and development
59 mq patch management and development
60 notify hook extension to email notifications on commits/pushes
60 notify hook extension to email notifications on commits/pushes
61
61
62 use "hg -v help keyword" to show aliases and global options
62 use "hg -v help keyword" to show aliases and global options
63 % hg kwdemo
63 % hg kwdemo
64 [extensions]
64 [extensions]
65 hgext.keyword =
65 hgext.keyword =
66 [keyword]
66 [keyword]
67 * =
67 * =
68 b = ignore
68 b = ignore
69 demo.txt =
69 demo.txt =
70 [keywordmaps]
70 [keywordmaps]
71 RCSFile = {file|basename},v
71 RCSFile = {file|basename},v
72 Author = {author|user}
72 Author = {author|user}
73 Header = {root}/{file},v {node|short} {date|utcdate} {author|user}
73 Header = {root}/{file},v {node|short} {date|utcdate} {author|user}
74 Source = {root}/{file},v
74 Source = {root}/{file},v
75 Date = {date|utcdate}
75 Date = {date|utcdate}
76 Id = {file|basename},v {node|short} {date|utcdate} {author|user}
76 Id = {file|basename},v {node|short} {date|utcdate} {author|user}
77 Revision = {node|short}
77 Revision = {node|short}
78 $RCSFile: demo.txt,v $
78 $RCSFile: demo.txt,v $
79 $Author: test $
79 $Author: test $
80 $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 $
81 $Source: /TMP/demo.txt,v $
81 $Source: /TMP/demo.txt,v $
82 $Date: 2000/00/00 00:00:00 $
82 $Date: 2000/00/00 00:00:00 $
83 $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 $
84 $Revision: xxxxxxxxxxxx $
84 $Revision: xxxxxxxxxxxx $
85 [extensions]
85 [extensions]
86 hgext.keyword =
86 hgext.keyword =
87 [keyword]
87 [keyword]
88 * =
88 * =
89 b = ignore
89 b = ignore
90 demo.txt =
90 demo.txt =
91 [keywordmaps]
91 [keywordmaps]
92 Branch = {branches}
92 Branch = {branches}
93 $Branch: demobranch $
93 $Branch: demobranch $
94 % kwshrink should exit silently in empty/invalid repo
94 % kwshrink should exit silently in empty/invalid repo
95 pulling from test-keyword.hg
95 pulling from test-keyword.hg
96 requesting all changes
96 requesting all changes
97 adding changesets
97 adding changesets
98 adding manifests
98 adding manifests
99 adding file changes
99 adding file changes
100 added 1 changesets with 1 changes to 1 files
100 added 1 changesets with 1 changes to 1 files
101 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
102 % cat
102 % cat
103 expand $Id$
103 expand $Id$
104 do not process $Id:
104 do not process $Id:
105 xxx $
105 xxx $
106 ignore $Id$
106 ignore $Id$
107 % addremove
107 % addremove
108 adding a
108 adding a
109 adding b
109 adding b
110 % status
110 % status
111 A a
111 A a
112 A b
112 A b
113 % default keyword expansion including commit hook
113 % default keyword expansion including commit hook
114 % interrupted commit should not change state or run commit hook
114 % interrupted commit should not change state or run commit hook
115 abort: empty commit message
115 abort: empty commit message
116 % status
116 % status
117 A a
117 A a
118 A b
118 A b
119 % commit
119 % commit
120 a
120 a
121 b
121 b
122 overwriting a expanding keywords
122 overwriting a expanding keywords
123 running hook commit.test: cp a hooktest
123 running hook commit.test: cp a hooktest
124 committed changeset 1:ef63ca68695bc9495032c6fda1350c71e6d256e9
124 committed changeset 1:ef63ca68695bc9495032c6fda1350c71e6d256e9
125 % status
125 % status
126 ? hooktest
126 ? hooktest
127 % identify
127 % identify
128 ef63ca68695b
128 ef63ca68695b
129 % cat
129 % cat
130 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
130 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
131 do not process $Id:
131 do not process $Id:
132 xxx $
132 xxx $
133 ignore $Id$
133 ignore $Id$
134 % hg cat
134 % hg cat
135 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
135 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
136 do not process $Id:
136 do not process $Id:
137 xxx $
137 xxx $
138 ignore $Id$
138 ignore $Id$
139 a
139 a
140 % diff a hooktest
140 % diff a hooktest
141 % removing commit hook from config
141 % removing commit hook from config
142 % bundle
142 % bundle
143 2 changesets found
143 2 changesets found
144 % notify on pull to check whether keywords stay as is in email
144 % notify on pull to check whether keywords stay as is in email
145 % ie. if patch.diff wrapper acts as it should
145 % ie. if patch.diff wrapper acts as it should
146 % pull from bundle
146 % pull from bundle
147 pulling from ../kw.hg
147 pulling from ../kw.hg
148 requesting all changes
148 requesting all changes
149 adding changesets
149 adding changesets
150 adding manifests
150 adding manifests
151 adding file changes
151 adding file changes
152 added 2 changesets with 3 changes to 3 files
152 added 2 changesets with 3 changes to 3 files
153
153
154 diff -r 000000000000 -r a2392c293916 sym
154 diff -r 000000000000 -r a2392c293916 sym
155 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
155 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
156 +++ b/sym Sat Feb 09 20:25:47 2008 +0100
156 +++ b/sym Sat Feb 09 20:25:47 2008 +0100
157 @@ -0,0 +1,1 @@
157 @@ -0,0 +1,1 @@
158 +a
158 +a
159 \ No newline at end of file
159 \ No newline at end of file
160
160
161 diff -r a2392c293916 -r ef63ca68695b a
161 diff -r a2392c293916 -r ef63ca68695b a
162 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
162 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
163 +++ b/a Thu Jan 01 00:00:00 1970 +0000
163 +++ b/a Thu Jan 01 00:00:00 1970 +0000
164 @@ -0,0 +1,3 @@
164 @@ -0,0 +1,3 @@
165 +expand $Id$
165 +expand $Id$
166 +do not process $Id:
166 +do not process $Id:
167 +xxx $
167 +xxx $
168 diff -r a2392c293916 -r ef63ca68695b b
168 diff -r a2392c293916 -r ef63ca68695b b
169 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
169 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
170 +++ b/b Thu Jan 01 00:00:00 1970 +0000
170 +++ b/b Thu Jan 01 00:00:00 1970 +0000
171 @@ -0,0 +1,1 @@
171 @@ -0,0 +1,1 @@
172 +ignore $Id$
172 +ignore $Id$
173 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
173 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
174 % remove notify config
174 % remove notify config
175 % touch
175 % touch
176 % status
176 % status
177 % update
177 % update
178 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
178 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
179 % cat
179 % cat
180 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
180 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
181 do not process $Id:
181 do not process $Id:
182 xxx $
182 xxx $
183 ignore $Id$
183 ignore $Id$
184 % check whether expansion is filewise
184 % check whether expansion is filewise
185 % commit c
185 % commit c
186 adding c
186 adding c
187 % force expansion
187 % force expansion
188 overwriting a expanding keywords
188 overwriting a expanding keywords
189 overwriting c expanding keywords
189 overwriting c expanding keywords
190 % compare changenodes in a c
190 % compare changenodes in a c
191 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
191 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
192 do not process $Id:
192 do not process $Id:
193 xxx $
193 xxx $
194 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
194 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
195 tests for different changenodes
195 tests for different changenodes
196 % qinit -c
196 % qinit -c
197 % qimport
197 % qimport
198 % qcommit
198 % qcommit
199 % keywords should not be expanded in patch
199 % keywords should not be expanded in patch
200 # HG changeset patch
200 # HG changeset patch
201 # User User Name <user@example.com>
201 # User User Name <user@example.com>
202 # Date 1 0
202 # Date 1 0
203 # Node ID 40a904bbbe4cd4ab0a1f28411e35db26341a40ad
203 # Node ID 40a904bbbe4cd4ab0a1f28411e35db26341a40ad
204 # Parent ef63ca68695bc9495032c6fda1350c71e6d256e9
204 # Parent ef63ca68695bc9495032c6fda1350c71e6d256e9
205 cndiff
205 cndiff
206
206
207 diff -r ef63ca68695b -r 40a904bbbe4c c
207 diff -r ef63ca68695b -r 40a904bbbe4c c
208 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
208 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
209 +++ b/c Thu Jan 01 00:00:01 1970 +0000
209 +++ b/c Thu Jan 01 00:00:01 1970 +0000
210 @@ -0,0 +1,2 @@
210 @@ -0,0 +1,2 @@
211 +$Id$
211 +$Id$
212 +tests for different changenodes
212 +tests for different changenodes
213 % qpop
213 % qpop
214 patch queue now empty
214 patch queue now empty
215 % qgoto - should imply qpush
215 % qgoto - should imply qpush
216 applying mqtest.diff
216 applying mqtest.diff
217 now at: mqtest.diff
217 now at: mqtest.diff
218 % cat
218 % cat
219 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
219 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
220 tests for different changenodes
220 tests for different changenodes
221 % qpop and move on
221 % qpop and move on
222 patch queue now empty
222 patch queue now empty
223 % copy
223 % copy
224 % kwfiles added
224 % kwfiles added
225 a
225 a
226 c
226 c
227 % commit
227 % commit
228 c
228 c
229 c: copy a:0045e12f6c5791aac80ca6cbfd97709a88307292
229 c: copy a:0045e12f6c5791aac80ca6cbfd97709a88307292
230 overwriting c expanding keywords
230 overwriting c expanding keywords
231 committed changeset 2:e22d299ac0c2bd8897b3df5114374b9e4d4ca62f
231 committed changeset 2:e22d299ac0c2bd8897b3df5114374b9e4d4ca62f
232 % cat a c
232 % cat a c
233 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
233 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
234 do not process $Id:
234 do not process $Id:
235 xxx $
235 xxx $
236 expand $Id: c,v e22d299ac0c2 1970/01/01 00:00:01 user $
236 expand $Id: c,v e22d299ac0c2 1970/01/01 00:00:01 user $
237 do not process $Id:
237 do not process $Id:
238 xxx $
238 xxx $
239 % touch copied c
239 % touch copied c
240 % status
240 % status
241 % kwfiles
241 % kwfiles
242 a
242 a
243 c
243 c
244 % diff --rev
244 % diff --rev
245 diff -r ef63ca68695b c
245 diff -r ef63ca68695b c
246 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
246 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
247 @@ -0,0 +1,3 @@
247 @@ -0,0 +1,3 @@
248 +expand $Id$
248 +expand $Id$
249 +do not process $Id:
249 +do not process $Id:
250 +xxx $
250 +xxx $
251 % rollback
251 % rollback
252 rolling back last transaction
252 rolling back last transaction
253 % status
253 % status
254 A c
254 A c
255 % update -C
255 % update -C
256 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
256 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
257 % custom keyword expansion
257 % custom keyword expansion
258 % try with kwdemo
258 % try with kwdemo
259 [extensions]
259 [extensions]
260 hgext.keyword =
260 hgext.keyword =
261 [keyword]
261 [keyword]
262 * =
262 * =
263 b = ignore
263 b = ignore
264 demo.txt =
264 demo.txt =
265 [keywordmaps]
265 [keywordmaps]
266 Xinfo = {author}: {desc}
266 Xinfo = {author}: {desc}
267 $Xinfo: test: hg keyword config and expansion example $
267 $Xinfo: test: hg keyword config and expansion example $
268 % cat
268 % cat
269 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
269 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
270 do not process $Id:
270 do not process $Id:
271 xxx $
271 xxx $
272 ignore $Id$
272 ignore $Id$
273 % hg cat
273 % hg cat
274 expand $Id: a ef63ca68695b Thu, 01 Jan 1970 00:00:00 +0000 user $
274 expand $Id: a ef63ca68695b Thu, 01 Jan 1970 00:00:00 +0000 user $
275 do not process $Id:
275 do not process $Id:
276 xxx $
276 xxx $
277 ignore $Id$
277 ignore $Id$
278 a
278 a
279 % interrupted commit should not change state
279 % interrupted commit should not change state
280 abort: empty commit message
280 abort: empty commit message
281 % status
281 % status
282 M a
282 M a
283 ? c
283 ? c
284 ? log
284 ? log
285 % commit
285 % commit
286 a
286 a
287 overwriting a expanding keywords
287 overwriting a expanding keywords
288 committed changeset 2:bb948857c743469b22bbf51f7ec8112279ca5d83
288 committed changeset 2:bb948857c743469b22bbf51f7ec8112279ca5d83
289 % status
289 % status
290 ? c
290 ? c
291 % verify
291 % verify
292 checking changesets
292 checking changesets
293 checking manifests
293 checking manifests
294 crosschecking files in changesets and manifests
294 crosschecking files in changesets and manifests
295 checking files
295 checking files
296 3 files, 3 changesets, 4 total revisions
296 3 files, 3 changesets, 4 total revisions
297 % cat
297 % cat
298 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
298 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
299 do not process $Id:
299 do not process $Id:
300 xxx $
300 xxx $
301 $Xinfo: User Name <user@example.com>: firstline $
301 $Xinfo: User Name <user@example.com>: firstline $
302 ignore $Id$
302 ignore $Id$
303 % hg cat
303 % hg cat
304 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
304 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
305 do not process $Id:
305 do not process $Id:
306 xxx $
306 xxx $
307 $Xinfo: User Name <user@example.com>: firstline $
307 $Xinfo: User Name <user@example.com>: firstline $
308 ignore $Id$
308 ignore $Id$
309 a
309 a
310 % annotate
310 % annotate
311 1: expand $Id$
311 1: expand $Id$
312 1: do not process $Id:
312 1: do not process $Id:
313 1: xxx $
313 1: xxx $
314 2: $Xinfo$
314 2: $Xinfo$
315 % remove
315 % remove
316 committed changeset 3:d14c712653769de926994cf7fbb06c8fbd68f012
316 committed changeset 3:d14c712653769de926994cf7fbb06c8fbd68f012
317 % status
317 % status
318 ? c
318 ? c
319 % rollback
319 % rollback
320 rolling back last transaction
320 rolling back last transaction
321 % status
321 % status
322 R a
322 R a
323 ? c
323 ? c
324 % revert a
324 % revert a
325 % cat a
325 % cat a
326 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
326 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
327 do not process $Id:
327 do not process $Id:
328 xxx $
328 xxx $
329 $Xinfo: User Name <user@example.com>: firstline $
329 $Xinfo: User Name <user@example.com>: firstline $
330 % clone to test incoming
330 % clone to test incoming
331 requesting all changes
331 requesting all changes
332 adding changesets
332 adding changesets
333 adding manifests
333 adding manifests
334 adding file changes
334 adding file changes
335 added 2 changesets with 3 changes to 3 files
335 added 2 changesets with 3 changes to 3 files
336 updating working directory
336 updating working directory
337 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
337 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
338 % incoming
338 % incoming
339 comparing with test-keyword/Test
339 comparing with test-keyword/Test
340 searching for changes
340 searching for changes
341 changeset: 2:bb948857c743
341 changeset: 2:bb948857c743
342 tag: tip
342 tag: tip
343 user: User Name <user@example.com>
343 user: User Name <user@example.com>
344 date: Thu Jan 01 00:00:02 1970 +0000
344 date: Thu Jan 01 00:00:02 1970 +0000
345 summary: firstline
345 summary: firstline
346
346
347 % commit rejecttest
347 % commit rejecttest
348 a
348 a
349 overwriting a expanding keywords
349 overwriting a expanding keywords
350 committed changeset 2:85e279d709ffc28c9fdd1b868570985fc3d87082
350 committed changeset 2:85e279d709ffc28c9fdd1b868570985fc3d87082
351 % export
351 % export
352 % import
352 % import
353 applying ../rejecttest.diff
353 applying ../rejecttest.diff
354 % cat
354 % cat
355 expand $Id: a 4e0994474d25 Thu, 01 Jan 1970 00:00:03 +0000 user $ rejecttest
355 expand $Id: a 4e0994474d25 Thu, 01 Jan 1970 00:00:03 +0000 user $ rejecttest
356 do not process $Id: rejecttest
356 do not process $Id: rejecttest
357 xxx $
357 xxx $
358 $Xinfo: User Name <user@example.com>: rejects? $
358 $Xinfo: User Name <user@example.com>: rejects? $
359 ignore $Id$
359 ignore $Id$
360
360
361 % rollback
361 % rollback
362 rolling back last transaction
362 rolling back last transaction
363 % clean update
363 % clean update
364 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
364 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
365 % kwexpand/kwshrink on selected files
365 % kwexpand/kwshrink on selected files
366 % copy a x/a
366 % copy a x/a
367 % kwexpand a
367 % kwexpand a
368 overwriting a expanding keywords
368 overwriting a expanding keywords
369 % kwexpand x/a should abort
369 % kwexpand x/a should abort
370 abort: outstanding uncommitted changes
370 abort: outstanding uncommitted changes
371 x/a
371 x/a
372 x/a: copy a:779c764182ce5d43e2b1eb66ce06d7b47bfe342e
372 x/a: copy a:779c764182ce5d43e2b1eb66ce06d7b47bfe342e
373 overwriting x/a expanding keywords
373 overwriting x/a expanding keywords
374 committed changeset 3:cfa68229c1167443337266ebac453c73b1d5d16e
374 committed changeset 3:cfa68229c1167443337266ebac453c73b1d5d16e
375 % cat a
375 % cat a
376 expand $Id: x/a cfa68229c116 Thu, 01 Jan 1970 00:00:03 +0000 user $
376 expand $Id: x/a cfa68229c116 Thu, 01 Jan 1970 00:00:03 +0000 user $
377 do not process $Id:
377 do not process $Id:
378 xxx $
378 xxx $
379 $Xinfo: User Name <user@example.com>: xa $
379 $Xinfo: User Name <user@example.com>: xa $
380 % kwshrink a inside directory x
380 % kwshrink a inside directory x
381 overwriting x/a shrinking keywords
381 overwriting x/a shrinking keywords
382 % cat a
382 % cat a
383 expand $Id$
383 expand $Id$
384 do not process $Id:
384 do not process $Id:
385 xxx $
385 xxx $
386 $Xinfo$
386 $Xinfo$
387 % kwexpand nonexistent
387 % kwexpand nonexistent
388 nonexistent:
388 nonexistent:
389 % hg serve
389 % hg serve
390 % expansion
390 % expansion
391 % hgweb file
391 % hgweb file
392 200 Script output follows
392 200 Script output follows
393
393
394 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
394 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
395 do not process $Id:
395 do not process $Id:
396 xxx $
396 xxx $
397 $Xinfo: User Name <user@example.com>: firstline $
397 $Xinfo: User Name <user@example.com>: firstline $
398 % no expansion
398 % no expansion
399 % hgweb annotate
399 % hgweb annotate
400 200 Script output follows
400 200 Script output follows
401
401
402
402
403 user@1: expand $Id$
403 user@1: expand $Id$
404 user@1: do not process $Id:
404 user@1: do not process $Id:
405 user@1: xxx $
405 user@1: xxx $
406 user@2: $Xinfo$
406 user@2: $Xinfo$
407
407
408
408
409
409
410
410
411 % hgweb changeset
411 % hgweb changeset
412 200 Script output follows
412 200 Script output follows
413
413
414
414
415 # HG changeset patch
415 # HG changeset patch
416 # User User Name <user@example.com>
416 # User User Name <user@example.com>
417 # Date 3 0
417 # Date 3 0
418 # Node ID cfa68229c1167443337266ebac453c73b1d5d16e
418 # Node ID cfa68229c1167443337266ebac453c73b1d5d16e
419 # Parent bb948857c743469b22bbf51f7ec8112279ca5d83
419 # Parent bb948857c743469b22bbf51f7ec8112279ca5d83
420 xa
420 xa
421
421
422 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
422 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
423 +++ b/x/a Thu Jan 01 00:00:03 1970 +0000
423 +++ b/x/a Thu Jan 01 00:00:03 1970 +0000
424 @@ -0,0 +1,4 @@
424 @@ -0,0 +1,4 @@
425 +expand $Id$
425 +expand $Id$
426 +do not process $Id:
426 +do not process $Id:
427 +xxx $
427 +xxx $
428 +$Xinfo$
428 +$Xinfo$
429
429
430 % hgweb filediff
430 % hgweb filediff
431 200 Script output follows
431 200 Script output follows
432
432
433
433
434 --- a/a Thu Jan 01 00:00:00 1970 +0000
434 --- a/a Thu Jan 01 00:00:00 1970 +0000
435 +++ b/a Thu Jan 01 00:00:02 1970 +0000
435 +++ b/a Thu Jan 01 00:00:02 1970 +0000
436 @@ -1,3 +1,4 @@
436 @@ -1,3 +1,4 @@
437 expand $Id$
437 expand $Id$
438 do not process $Id:
438 do not process $Id:
439 xxx $
439 xxx $
440 +$Xinfo$
440 +$Xinfo$
441
441
442
442
443
443
444
444
445 % errors encountered
445 % errors encountered
446 % merge/resolve
446 % merge/resolve
447 % simplemerge
447 % simplemerge
448 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
448 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
449 created new head
449 created new head
450 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
450 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
451 (branch merge, don't forget to commit)
451 (branch merge, don't forget to commit)
452 $Id: m 8731e1dadc99 Thu, 01 Jan 1970 00:00:00 +0000 test $
452 $Id: m 8731e1dadc99 Thu, 01 Jan 1970 00:00:00 +0000 test $
453 foo
453 foo
454 % conflict
454 % conflict
455 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
455 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
456 created new head
456 created new head
457 merging m
457 merging m
458 warning: conflicts during merge.
458 warning: conflicts during merge.
459 merging m failed!
459 merging m failed!
460 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
460 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
461 use 'hg resolve' to retry unresolved file merges or 'hg up --clean' to abandon
462 % keyword stays outside conflict zone
462 % keyword stays outside conflict zone
463 $Id$
463 $Id$
464 <<<<<<< local
464 <<<<<<< local
465 bar
465 bar
466 =======
466 =======
467 foo
467 foo
468 >>>>>>> other
468 >>>>>>> other
469 % resolve to local
469 % resolve to local
470 $Id: m 43dfd2854b5b Thu, 01 Jan 1970 00:00:00 +0000 test $
470 $Id: m 43dfd2854b5b Thu, 01 Jan 1970 00:00:00 +0000 test $
471 bar
471 bar
472 % switch off expansion
472 % switch off expansion
473 % kwshrink with unknown file u
473 % kwshrink with unknown file u
474 overwriting a shrinking keywords
474 overwriting a shrinking keywords
475 overwriting m shrinking keywords
475 overwriting m shrinking keywords
476 overwriting x/a shrinking keywords
476 overwriting x/a shrinking keywords
477 % cat
477 % cat
478 expand $Id$
478 expand $Id$
479 do not process $Id:
479 do not process $Id:
480 xxx $
480 xxx $
481 $Xinfo$
481 $Xinfo$
482 ignore $Id$
482 ignore $Id$
483 % hg cat
483 % hg cat
484 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
484 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
485 do not process $Id:
485 do not process $Id:
486 xxx $
486 xxx $
487 $Xinfo: User Name <user@example.com>: firstline $
487 $Xinfo: User Name <user@example.com>: firstline $
488 ignore $Id$
488 ignore $Id$
489 a
489 a
490 % cat
490 % cat
491 expand $Id$
491 expand $Id$
492 do not process $Id:
492 do not process $Id:
493 xxx $
493 xxx $
494 $Xinfo$
494 $Xinfo$
495 ignore $Id$
495 ignore $Id$
496 % hg cat
496 % hg cat
497 expand $Id$
497 expand $Id$
498 do not process $Id:
498 do not process $Id:
499 xxx $
499 xxx $
500 $Xinfo$
500 $Xinfo$
501 ignore $Id$
501 ignore $Id$
502 a
502 a
General Comments 0
You need to be logged in to leave comments. Login now