##// END OF EJS Templates
extdiff: use absolute paths for any temporary files...
jfh -
r13758:5c0e1222 default
parent child Browse files
Show More
@@ -1,325 +1,325 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 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to allow external programs to compare revisions
8 '''command to allow external programs to compare revisions
9
9
10 The extdiff Mercurial extension allows you to use external programs
10 The extdiff Mercurial extension allows you to use external programs
11 to compare revisions, or revision with working directory. The external
11 to compare revisions, or revision with working directory. The external
12 diff programs are called with a configurable set of options and two
12 diff programs are called with a configurable set of options and two
13 non-option arguments: paths to directories containing snapshots of
13 non-option arguments: paths to directories containing snapshots of
14 files to compare.
14 files to compare.
15
15
16 The extdiff extension also allows to configure new diff commands, so
16 The extdiff extension also allows to configure new diff commands, so
17 you do not need to type :hg:`extdiff -p kdiff3` always. ::
17 you do not need to type :hg:`extdiff -p kdiff3` always. ::
18
18
19 [extdiff]
19 [extdiff]
20 # add new command that runs GNU diff(1) in 'context diff' mode
20 # add new command that runs GNU diff(1) in 'context diff' mode
21 cdiff = gdiff -Nprc5
21 cdiff = gdiff -Nprc5
22 ## or the old way:
22 ## or the old way:
23 #cmd.cdiff = gdiff
23 #cmd.cdiff = gdiff
24 #opts.cdiff = -Nprc5
24 #opts.cdiff = -Nprc5
25
25
26 # add new command called vdiff, runs kdiff3
26 # add new command called vdiff, runs kdiff3
27 vdiff = kdiff3
27 vdiff = kdiff3
28
28
29 # add new command called meld, runs meld (no need to name twice)
29 # add new command called meld, runs meld (no need to name twice)
30 meld =
30 meld =
31
31
32 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
32 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
33 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
33 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
34 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
34 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
35 # your .vimrc
35 # your .vimrc
36 vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)'
36 vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)'
37
37
38 Tool arguments can include variables that are expanded at runtime::
38 Tool arguments can include variables that are expanded at runtime::
39
39
40 $parent1, $plabel1 - filename, descriptive label of first parent
40 $parent1, $plabel1 - filename, descriptive label of first parent
41 $child, $clabel - filename, descriptive label of child revision
41 $child, $clabel - filename, descriptive label of child revision
42 $parent2, $plabel2 - filename, descriptive label of second parent
42 $parent2, $plabel2 - filename, descriptive label of second parent
43 $parent is an alias for $parent1.
43 $parent is an alias for $parent1.
44
44
45 The extdiff extension will look in your [diff-tools] and [merge-tools]
45 The extdiff extension will look in your [diff-tools] and [merge-tools]
46 sections for diff tool arguments, when none are specified in [extdiff].
46 sections for diff tool arguments, when none are specified in [extdiff].
47
47
48 ::
48 ::
49
49
50 [extdiff]
50 [extdiff]
51 kdiff3 =
51 kdiff3 =
52
52
53 [diff-tools]
53 [diff-tools]
54 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
54 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
55
55
56 You can use -I/-X and list of file or directory names like normal
56 You can use -I/-X and list of file or directory names like normal
57 :hg:`diff` command. The extdiff extension makes snapshots of only
57 :hg:`diff` command. The extdiff extension makes snapshots of only
58 needed files, so running the external diff program will actually be
58 needed files, so running the external diff program will actually be
59 pretty fast (at least faster than having to compare the entire tree).
59 pretty fast (at least faster than having to compare the entire tree).
60 '''
60 '''
61
61
62 from mercurial.i18n import _
62 from mercurial.i18n import _
63 from mercurial.node import short, nullid
63 from mercurial.node import short, nullid
64 from mercurial import cmdutil, util, commands, encoding
64 from mercurial import cmdutil, util, commands, encoding
65 import os, shlex, shutil, tempfile, re
65 import os, shlex, shutil, tempfile, re
66
66
67 def snapshot(ui, repo, files, node, tmproot):
67 def snapshot(ui, repo, files, node, tmproot):
68 '''snapshot files as of some revision
68 '''snapshot files as of some revision
69 if not using snapshot, -I/-X does not work and recursive diff
69 if not using snapshot, -I/-X does not work and recursive diff
70 in tools like kdiff3 and meld displays too many files.'''
70 in tools like kdiff3 and meld displays too many files.'''
71 dirname = os.path.basename(repo.root)
71 dirname = os.path.basename(repo.root)
72 if dirname == "":
72 if dirname == "":
73 dirname = "root"
73 dirname = "root"
74 if node is not None:
74 if node is not None:
75 dirname = '%s.%s' % (dirname, short(node))
75 dirname = '%s.%s' % (dirname, short(node))
76 base = os.path.join(tmproot, dirname)
76 base = os.path.join(tmproot, dirname)
77 os.mkdir(base)
77 os.mkdir(base)
78 if node is not None:
78 if node is not None:
79 ui.note(_('making snapshot of %d files from rev %s\n') %
79 ui.note(_('making snapshot of %d files from rev %s\n') %
80 (len(files), short(node)))
80 (len(files), short(node)))
81 else:
81 else:
82 ui.note(_('making snapshot of %d files from working directory\n') %
82 ui.note(_('making snapshot of %d files from working directory\n') %
83 (len(files)))
83 (len(files)))
84 wopener = util.opener(base)
84 wopener = util.opener(base)
85 fns_and_mtime = []
85 fns_and_mtime = []
86 ctx = repo[node]
86 ctx = repo[node]
87 for fn in files:
87 for fn in files:
88 wfn = util.pconvert(fn)
88 wfn = util.pconvert(fn)
89 if not wfn in ctx:
89 if not wfn in ctx:
90 # File doesn't exist; could be a bogus modify
90 # File doesn't exist; could be a bogus modify
91 continue
91 continue
92 ui.note(' %s\n' % wfn)
92 ui.note(' %s\n' % wfn)
93 dest = os.path.join(base, wfn)
93 dest = os.path.join(base, wfn)
94 fctx = ctx[wfn]
94 fctx = ctx[wfn]
95 data = repo.wwritedata(wfn, fctx.data())
95 data = repo.wwritedata(wfn, fctx.data())
96 if 'l' in fctx.flags():
96 if 'l' in fctx.flags():
97 wopener.symlink(data, wfn)
97 wopener.symlink(data, wfn)
98 else:
98 else:
99 wopener(wfn, 'w').write(data)
99 wopener(wfn, 'w').write(data)
100 if 'x' in fctx.flags():
100 if 'x' in fctx.flags():
101 util.set_flags(dest, False, True)
101 util.set_flags(dest, False, True)
102 if node is None:
102 if node is None:
103 fns_and_mtime.append((dest, repo.wjoin(fn), os.path.getmtime(dest)))
103 fns_and_mtime.append((dest, repo.wjoin(fn), os.path.getmtime(dest)))
104 return dirname, fns_and_mtime
104 return dirname, fns_and_mtime
105
105
106 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
106 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
107 '''Do the actuall diff:
107 '''Do the actuall diff:
108
108
109 - copy to a temp structure if diffing 2 internal revisions
109 - copy to a temp structure if diffing 2 internal revisions
110 - copy to a temp structure if diffing working revision with
110 - copy to a temp structure if diffing working revision with
111 another one and more than 1 file is changed
111 another one and more than 1 file is changed
112 - just invoke the diff for a single file in the working dir
112 - just invoke the diff for a single file in the working dir
113 '''
113 '''
114
114
115 revs = opts.get('rev')
115 revs = opts.get('rev')
116 change = opts.get('change')
116 change = opts.get('change')
117 args = ' '.join(diffopts)
117 args = ' '.join(diffopts)
118 do3way = '$parent2' in args
118 do3way = '$parent2' in args
119
119
120 if revs and change:
120 if revs and change:
121 msg = _('cannot specify --rev and --change at the same time')
121 msg = _('cannot specify --rev and --change at the same time')
122 raise util.Abort(msg)
122 raise util.Abort(msg)
123 elif change:
123 elif change:
124 node2 = cmdutil.revsingle(repo, change, None).node()
124 node2 = cmdutil.revsingle(repo, change, None).node()
125 node1a, node1b = repo.changelog.parents(node2)
125 node1a, node1b = repo.changelog.parents(node2)
126 else:
126 else:
127 node1a, node2 = cmdutil.revpair(repo, revs)
127 node1a, node2 = cmdutil.revpair(repo, revs)
128 if not revs:
128 if not revs:
129 node1b = repo.dirstate.parents()[1]
129 node1b = repo.dirstate.parents()[1]
130 else:
130 else:
131 node1b = nullid
131 node1b = nullid
132
132
133 # Disable 3-way merge if there is only one parent
133 # Disable 3-way merge if there is only one parent
134 if do3way:
134 if do3way:
135 if node1b == nullid:
135 if node1b == nullid:
136 do3way = False
136 do3way = False
137
137
138 matcher = cmdutil.match(repo, pats, opts)
138 matcher = cmdutil.match(repo, pats, opts)
139 mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3])
139 mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3])
140 if do3way:
140 if do3way:
141 mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher)[:3])
141 mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher)[:3])
142 else:
142 else:
143 mod_b, add_b, rem_b = set(), set(), set()
143 mod_b, add_b, rem_b = set(), set(), set()
144 modadd = mod_a | add_a | mod_b | add_b
144 modadd = mod_a | add_a | mod_b | add_b
145 common = modadd | rem_a | rem_b
145 common = modadd | rem_a | rem_b
146 if not common:
146 if not common:
147 return 0
147 return 0
148
148
149 tmproot = tempfile.mkdtemp(prefix='extdiff.')
149 tmproot = tempfile.mkdtemp(prefix='extdiff.')
150 try:
150 try:
151 # Always make a copy of node1a (and node1b, if applicable)
151 # Always make a copy of node1a (and node1b, if applicable)
152 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
152 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
153 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0]
153 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0]
154 rev1a = '@%d' % repo[node1a].rev()
154 rev1a = '@%d' % repo[node1a].rev()
155 if do3way:
155 if do3way:
156 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
156 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
157 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0]
157 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0]
158 rev1b = '@%d' % repo[node1b].rev()
158 rev1b = '@%d' % repo[node1b].rev()
159 else:
159 else:
160 dir1b = None
160 dir1b = None
161 rev1b = ''
161 rev1b = ''
162
162
163 fns_and_mtime = []
163 fns_and_mtime = []
164
164
165 # If node2 in not the wc or there is >1 change, copy it
165 # If node2 in not the wc or there is >1 change, copy it
166 dir2root = ''
166 dir2root = ''
167 rev2 = ''
167 rev2 = ''
168 if node2:
168 if node2:
169 dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0]
169 dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0]
170 rev2 = '@%d' % repo[node2].rev()
170 rev2 = '@%d' % repo[node2].rev()
171 elif len(common) > 1:
171 elif len(common) > 1:
172 #we only actually need to get the files to copy back to
172 #we only actually need to get the files to copy back to
173 #the working dir in this case (because the other cases
173 #the working dir in this case (because the other cases
174 #are: diffing 2 revisions or single file -- in which case
174 #are: diffing 2 revisions or single file -- in which case
175 #the file is already directly passed to the diff tool).
175 #the file is already directly passed to the diff tool).
176 dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot)
176 dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot)
177 else:
177 else:
178 # This lets the diff tool open the changed file directly
178 # This lets the diff tool open the changed file directly
179 dir2 = ''
179 dir2 = ''
180 dir2root = repo.root
180 dir2root = repo.root
181
181
182 label1a = rev1a
182 label1a = rev1a
183 label1b = rev1b
183 label1b = rev1b
184 label2 = rev2
184 label2 = rev2
185
185
186 # If only one change, diff the files instead of the directories
186 # If only one change, diff the files instead of the directories
187 # Handle bogus modifies correctly by checking if the files exist
187 # Handle bogus modifies correctly by checking if the files exist
188 if len(common) == 1:
188 if len(common) == 1:
189 common_file = util.localpath(common.pop())
189 common_file = util.localpath(common.pop())
190 dir1a = os.path.join(dir1a, common_file)
190 dir1a = os.path.join(tmproot, dir1a, common_file)
191 label1a = common_file + rev1a
191 label1a = common_file + rev1a
192 if not os.path.isfile(os.path.join(tmproot, dir1a)):
192 if not os.path.isfile(dir1a):
193 dir1a = os.devnull
193 dir1a = os.devnull
194 if do3way:
194 if do3way:
195 dir1b = os.path.join(dir1b, common_file)
195 dir1b = os.path.join(tmproot, dir1b, common_file)
196 label1b = common_file + rev1b
196 label1b = common_file + rev1b
197 if not os.path.isfile(os.path.join(tmproot, dir1b)):
197 if not os.path.isfile(dir1b):
198 dir1b = os.devnull
198 dir1b = os.devnull
199 dir2 = os.path.join(dir2root, dir2, common_file)
199 dir2 = os.path.join(dir2root, dir2, common_file)
200 label2 = common_file + rev2
200 label2 = common_file + rev2
201
201
202 # Function to quote file/dir names in the argument string.
202 # Function to quote file/dir names in the argument string.
203 # When not operating in 3-way mode, an empty string is
203 # When not operating in 3-way mode, an empty string is
204 # returned for parent2
204 # returned for parent2
205 replace = dict(parent=dir1a, parent1=dir1a, parent2=dir1b,
205 replace = dict(parent=dir1a, parent1=dir1a, parent2=dir1b,
206 plabel1=label1a, plabel2=label1b,
206 plabel1=label1a, plabel2=label1b,
207 clabel=label2, child=dir2)
207 clabel=label2, child=dir2)
208 def quote(match):
208 def quote(match):
209 key = match.group()[1:]
209 key = match.group()[1:]
210 if not do3way and key == 'parent2':
210 if not do3way and key == 'parent2':
211 return ''
211 return ''
212 return util.shellquote(replace[key])
212 return util.shellquote(replace[key])
213
213
214 # Match parent2 first, so 'parent1?' will match both parent1 and parent
214 # Match parent2 first, so 'parent1?' will match both parent1 and parent
215 regex = '\$(parent2|parent1?|child|plabel1|plabel2|clabel)'
215 regex = '\$(parent2|parent1?|child|plabel1|plabel2|clabel)'
216 if not do3way and not re.search(regex, args):
216 if not do3way and not re.search(regex, args):
217 args += ' $parent1 $child'
217 args += ' $parent1 $child'
218 args = re.sub(regex, quote, args)
218 args = re.sub(regex, quote, args)
219 cmdline = util.shellquote(diffcmd) + ' ' + args
219 cmdline = util.shellquote(diffcmd) + ' ' + args
220
220
221 ui.debug('running %r in %s\n' % (cmdline, tmproot))
221 ui.debug('running %r in %s\n' % (cmdline, tmproot))
222 util.system(cmdline, cwd=tmproot)
222 util.system(cmdline, cwd=tmproot)
223
223
224 for copy_fn, working_fn, mtime in fns_and_mtime:
224 for copy_fn, working_fn, mtime in fns_and_mtime:
225 if os.path.getmtime(copy_fn) != mtime:
225 if os.path.getmtime(copy_fn) != mtime:
226 ui.debug('file changed while diffing. '
226 ui.debug('file changed while diffing. '
227 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
227 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
228 util.copyfile(copy_fn, working_fn)
228 util.copyfile(copy_fn, working_fn)
229
229
230 return 1
230 return 1
231 finally:
231 finally:
232 ui.note(_('cleaning up temp directory\n'))
232 ui.note(_('cleaning up temp directory\n'))
233 shutil.rmtree(tmproot)
233 shutil.rmtree(tmproot)
234
234
235 def extdiff(ui, repo, *pats, **opts):
235 def extdiff(ui, repo, *pats, **opts):
236 '''use external program to diff repository (or selected files)
236 '''use external program to diff repository (or selected files)
237
237
238 Show differences between revisions for the specified files, using
238 Show differences between revisions for the specified files, using
239 an external program. The default program used is diff, with
239 an external program. The default program used is diff, with
240 default options "-Npru".
240 default options "-Npru".
241
241
242 To select a different program, use the -p/--program option. The
242 To select a different program, use the -p/--program option. The
243 program will be passed the names of two directories to compare. To
243 program will be passed the names of two directories to compare. To
244 pass additional options to the program, use -o/--option. These
244 pass additional options to the program, use -o/--option. These
245 will be passed before the names of the directories to compare.
245 will be passed before the names of the directories to compare.
246
246
247 When two revision arguments are given, then changes are shown
247 When two revision arguments are given, then changes are shown
248 between those revisions. If only one revision is specified then
248 between those revisions. If only one revision is specified then
249 that revision is compared to the working directory, and, when no
249 that revision is compared to the working directory, and, when no
250 revisions are specified, the working directory files are compared
250 revisions are specified, the working directory files are compared
251 to its parent.'''
251 to its parent.'''
252 program = opts.get('program')
252 program = opts.get('program')
253 option = opts.get('option')
253 option = opts.get('option')
254 if not program:
254 if not program:
255 program = 'diff'
255 program = 'diff'
256 option = option or ['-Npru']
256 option = option or ['-Npru']
257 return dodiff(ui, repo, program, option, pats, opts)
257 return dodiff(ui, repo, program, option, pats, opts)
258
258
259 cmdtable = {
259 cmdtable = {
260 "extdiff":
260 "extdiff":
261 (extdiff,
261 (extdiff,
262 [('p', 'program', '',
262 [('p', 'program', '',
263 _('comparison program to run'), _('CMD')),
263 _('comparison program to run'), _('CMD')),
264 ('o', 'option', [],
264 ('o', 'option', [],
265 _('pass option to comparison program'), _('OPT')),
265 _('pass option to comparison program'), _('OPT')),
266 ('r', 'rev', [],
266 ('r', 'rev', [],
267 _('revision'), _('REV')),
267 _('revision'), _('REV')),
268 ('c', 'change', '',
268 ('c', 'change', '',
269 _('change made by revision'), _('REV')),
269 _('change made by revision'), _('REV')),
270 ] + commands.walkopts,
270 ] + commands.walkopts,
271 _('hg extdiff [OPT]... [FILE]...')),
271 _('hg extdiff [OPT]... [FILE]...')),
272 }
272 }
273
273
274 def uisetup(ui):
274 def uisetup(ui):
275 for cmd, path in ui.configitems('extdiff'):
275 for cmd, path in ui.configitems('extdiff'):
276 if cmd.startswith('cmd.'):
276 if cmd.startswith('cmd.'):
277 cmd = cmd[4:]
277 cmd = cmd[4:]
278 if not path:
278 if not path:
279 path = cmd
279 path = cmd
280 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
280 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
281 diffopts = diffopts and [diffopts] or []
281 diffopts = diffopts and [diffopts] or []
282 elif cmd.startswith('opts.'):
282 elif cmd.startswith('opts.'):
283 continue
283 continue
284 else:
284 else:
285 # command = path opts
285 # command = path opts
286 if path:
286 if path:
287 diffopts = shlex.split(path)
287 diffopts = shlex.split(path)
288 path = diffopts.pop(0)
288 path = diffopts.pop(0)
289 else:
289 else:
290 path, diffopts = cmd, []
290 path, diffopts = cmd, []
291 # look for diff arguments in [diff-tools] then [merge-tools]
291 # look for diff arguments in [diff-tools] then [merge-tools]
292 if diffopts == []:
292 if diffopts == []:
293 args = ui.config('diff-tools', cmd+'.diffargs') or \
293 args = ui.config('diff-tools', cmd+'.diffargs') or \
294 ui.config('merge-tools', cmd+'.diffargs')
294 ui.config('merge-tools', cmd+'.diffargs')
295 if args:
295 if args:
296 diffopts = shlex.split(args)
296 diffopts = shlex.split(args)
297 def save(cmd, path, diffopts):
297 def save(cmd, path, diffopts):
298 '''use closure to save diff command to use'''
298 '''use closure to save diff command to use'''
299 def mydiff(ui, repo, *pats, **opts):
299 def mydiff(ui, repo, *pats, **opts):
300 return dodiff(ui, repo, path, diffopts + opts['option'],
300 return dodiff(ui, repo, path, diffopts + opts['option'],
301 pats, opts)
301 pats, opts)
302 doc = _('''\
302 doc = _('''\
303 use %(path)s to diff repository (or selected files)
303 use %(path)s to diff repository (or selected files)
304
304
305 Show differences between revisions for the specified files, using
305 Show differences between revisions for the specified files, using
306 the %(path)s program.
306 the %(path)s program.
307
307
308 When two revision arguments are given, then changes are shown
308 When two revision arguments are given, then changes are shown
309 between those revisions. If only one revision is specified then
309 between those revisions. If only one revision is specified then
310 that revision is compared to the working directory, and, when no
310 that revision is compared to the working directory, and, when no
311 revisions are specified, the working directory files are compared
311 revisions are specified, the working directory files are compared
312 to its parent.\
312 to its parent.\
313 ''') % dict(path=util.uirepr(path))
313 ''') % dict(path=util.uirepr(path))
314
314
315 # We must translate the docstring right away since it is
315 # We must translate the docstring right away since it is
316 # used as a format string. The string will unfortunately
316 # used as a format string. The string will unfortunately
317 # be translated again in commands.helpcmd and this will
317 # be translated again in commands.helpcmd and this will
318 # fail when the docstring contains non-ASCII characters.
318 # fail when the docstring contains non-ASCII characters.
319 # Decoding the string to a Unicode string here (using the
319 # Decoding the string to a Unicode string here (using the
320 # right encoding) prevents that.
320 # right encoding) prevents that.
321 mydiff.__doc__ = doc.decode(encoding.encoding)
321 mydiff.__doc__ = doc.decode(encoding.encoding)
322 return mydiff
322 return mydiff
323 cmdtable[cmd] = (save(cmd, path, diffopts),
323 cmdtable[cmd] = (save(cmd, path, diffopts),
324 cmdtable['extdiff'][1][1:],
324 cmdtable['extdiff'][1][1:],
325 _('hg %s [OPTION]... [FILE]...') % cmd)
325 _('hg %s [OPTION]... [FILE]...') % cmd)
General Comments 0
You need to be logged in to leave comments. Login now