##// END OF EJS Templates
py3: make a pycompat.osdevnull, use it in extdiff...
Kyle Lippincott -
r44229:765a9c29 default
parent child Browse files
Show More
@@ -1,722 +1,722 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 If there is more than one file being compared and the "child" revision
16 If there is more than one file being compared and the "child" revision
17 is the working directory, any modifications made in the external diff
17 is the working directory, any modifications made in the external diff
18 program will be copied back to the working directory from the temporary
18 program will be copied back to the working directory from the temporary
19 directory.
19 directory.
20
20
21 The extdiff extension also allows you to configure new diff commands, so
21 The extdiff extension also allows you to configure new diff commands, so
22 you do not need to type :hg:`extdiff -p kdiff3` always. ::
22 you do not need to type :hg:`extdiff -p kdiff3` always. ::
23
23
24 [extdiff]
24 [extdiff]
25 # add new command that runs GNU diff(1) in 'context diff' mode
25 # add new command that runs GNU diff(1) in 'context diff' mode
26 cdiff = gdiff -Nprc5
26 cdiff = gdiff -Nprc5
27 ## or the old way:
27 ## or the old way:
28 #cmd.cdiff = gdiff
28 #cmd.cdiff = gdiff
29 #opts.cdiff = -Nprc5
29 #opts.cdiff = -Nprc5
30
30
31 # add new command called meld, runs meld (no need to name twice). If
31 # add new command called meld, runs meld (no need to name twice). If
32 # the meld executable is not available, the meld tool in [merge-tools]
32 # the meld executable is not available, the meld tool in [merge-tools]
33 # will be used, if available
33 # will be used, if available
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) Non
37 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
38 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
38 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
39 # your .vimrc
39 # your .vimrc
40 vimdiff = gvim -f "+next" \\
40 vimdiff = gvim -f "+next" \\
41 "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))"
41 "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))"
42
42
43 Tool arguments can include variables that are expanded at runtime::
43 Tool arguments can include variables that are expanded at runtime::
44
44
45 $parent1, $plabel1 - filename, descriptive label of first parent
45 $parent1, $plabel1 - filename, descriptive label of first parent
46 $child, $clabel - filename, descriptive label of child revision
46 $child, $clabel - filename, descriptive label of child revision
47 $parent2, $plabel2 - filename, descriptive label of second parent
47 $parent2, $plabel2 - filename, descriptive label of second parent
48 $root - repository root
48 $root - repository root
49 $parent is an alias for $parent1.
49 $parent is an alias for $parent1.
50
50
51 The extdiff extension will look in your [diff-tools] and [merge-tools]
51 The extdiff extension will look in your [diff-tools] and [merge-tools]
52 sections for diff tool arguments, when none are specified in [extdiff].
52 sections for diff tool arguments, when none are specified in [extdiff].
53
53
54 ::
54 ::
55
55
56 [extdiff]
56 [extdiff]
57 kdiff3 =
57 kdiff3 =
58
58
59 [diff-tools]
59 [diff-tools]
60 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
60 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
61
61
62 If a program has a graphical interface, it might be interesting to tell
62 If a program has a graphical interface, it might be interesting to tell
63 Mercurial about it. It will prevent the program from being mistakenly
63 Mercurial about it. It will prevent the program from being mistakenly
64 used in a terminal-only environment (such as an SSH terminal session),
64 used in a terminal-only environment (such as an SSH terminal session),
65 and will make :hg:`extdiff --per-file` open multiple file diffs at once
65 and will make :hg:`extdiff --per-file` open multiple file diffs at once
66 instead of one by one (if you still want to open file diffs one by one,
66 instead of one by one (if you still want to open file diffs one by one,
67 you can use the --confirm option).
67 you can use the --confirm option).
68
68
69 Declaring that a tool has a graphical interface can be done with the
69 Declaring that a tool has a graphical interface can be done with the
70 ``gui`` flag next to where ``diffargs`` are specified:
70 ``gui`` flag next to where ``diffargs`` are specified:
71
71
72 ::
72 ::
73
73
74 [diff-tools]
74 [diff-tools]
75 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
75 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
76 kdiff3.gui = true
76 kdiff3.gui = true
77
77
78 You can use -I/-X and list of file or directory names like normal
78 You can use -I/-X and list of file or directory names like normal
79 :hg:`diff` command. The extdiff extension makes snapshots of only
79 :hg:`diff` command. The extdiff extension makes snapshots of only
80 needed files, so running the external diff program will actually be
80 needed files, so running the external diff program will actually be
81 pretty fast (at least faster than having to compare the entire tree).
81 pretty fast (at least faster than having to compare the entire tree).
82 '''
82 '''
83
83
84 from __future__ import absolute_import
84 from __future__ import absolute_import
85
85
86 import os
86 import os
87 import re
87 import re
88 import shutil
88 import shutil
89 import stat
89 import stat
90 import subprocess
90 import subprocess
91
91
92 from mercurial.i18n import _
92 from mercurial.i18n import _
93 from mercurial.node import (
93 from mercurial.node import (
94 nullid,
94 nullid,
95 short,
95 short,
96 )
96 )
97 from mercurial import (
97 from mercurial import (
98 archival,
98 archival,
99 cmdutil,
99 cmdutil,
100 encoding,
100 encoding,
101 error,
101 error,
102 filemerge,
102 filemerge,
103 formatter,
103 formatter,
104 pycompat,
104 pycompat,
105 registrar,
105 registrar,
106 scmutil,
106 scmutil,
107 util,
107 util,
108 )
108 )
109 from mercurial.utils import (
109 from mercurial.utils import (
110 procutil,
110 procutil,
111 stringutil,
111 stringutil,
112 )
112 )
113
113
114 cmdtable = {}
114 cmdtable = {}
115 command = registrar.command(cmdtable)
115 command = registrar.command(cmdtable)
116
116
117 configtable = {}
117 configtable = {}
118 configitem = registrar.configitem(configtable)
118 configitem = registrar.configitem(configtable)
119
119
120 configitem(
120 configitem(
121 b'extdiff', br'opts\..*', default=b'', generic=True,
121 b'extdiff', br'opts\..*', default=b'', generic=True,
122 )
122 )
123
123
124 configitem(
124 configitem(
125 b'extdiff', br'gui\..*', generic=True,
125 b'extdiff', br'gui\..*', generic=True,
126 )
126 )
127
127
128 configitem(
128 configitem(
129 b'diff-tools', br'.*\.diffargs$', default=None, generic=True,
129 b'diff-tools', br'.*\.diffargs$', default=None, generic=True,
130 )
130 )
131
131
132 configitem(
132 configitem(
133 b'diff-tools', br'.*\.gui$', generic=True,
133 b'diff-tools', br'.*\.gui$', generic=True,
134 )
134 )
135
135
136 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
136 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
137 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
137 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
138 # be specifying the version(s) of Mercurial they are tested with, or
138 # be specifying the version(s) of Mercurial they are tested with, or
139 # leave the attribute unspecified.
139 # leave the attribute unspecified.
140 testedwith = b'ships-with-hg-core'
140 testedwith = b'ships-with-hg-core'
141
141
142
142
143 def snapshot(ui, repo, files, node, tmproot, listsubrepos):
143 def snapshot(ui, repo, files, node, tmproot, listsubrepos):
144 '''snapshot files as of some revision
144 '''snapshot files as of some revision
145 if not using snapshot, -I/-X does not work and recursive diff
145 if not using snapshot, -I/-X does not work and recursive diff
146 in tools like kdiff3 and meld displays too many files.'''
146 in tools like kdiff3 and meld displays too many files.'''
147 dirname = os.path.basename(repo.root)
147 dirname = os.path.basename(repo.root)
148 if dirname == b"":
148 if dirname == b"":
149 dirname = b"root"
149 dirname = b"root"
150 if node is not None:
150 if node is not None:
151 dirname = b'%s.%s' % (dirname, short(node))
151 dirname = b'%s.%s' % (dirname, short(node))
152 base = os.path.join(tmproot, dirname)
152 base = os.path.join(tmproot, dirname)
153 os.mkdir(base)
153 os.mkdir(base)
154 fnsandstat = []
154 fnsandstat = []
155
155
156 if node is not None:
156 if node is not None:
157 ui.note(
157 ui.note(
158 _(b'making snapshot of %d files from rev %s\n')
158 _(b'making snapshot of %d files from rev %s\n')
159 % (len(files), short(node))
159 % (len(files), short(node))
160 )
160 )
161 else:
161 else:
162 ui.note(
162 ui.note(
163 _(b'making snapshot of %d files from working directory\n')
163 _(b'making snapshot of %d files from working directory\n')
164 % (len(files))
164 % (len(files))
165 )
165 )
166
166
167 if files:
167 if files:
168 repo.ui.setconfig(b"ui", b"archivemeta", False)
168 repo.ui.setconfig(b"ui", b"archivemeta", False)
169
169
170 archival.archive(
170 archival.archive(
171 repo,
171 repo,
172 base,
172 base,
173 node,
173 node,
174 b'files',
174 b'files',
175 match=scmutil.matchfiles(repo, files),
175 match=scmutil.matchfiles(repo, files),
176 subrepos=listsubrepos,
176 subrepos=listsubrepos,
177 )
177 )
178
178
179 for fn in sorted(files):
179 for fn in sorted(files):
180 wfn = util.pconvert(fn)
180 wfn = util.pconvert(fn)
181 ui.note(b' %s\n' % wfn)
181 ui.note(b' %s\n' % wfn)
182
182
183 if node is None:
183 if node is None:
184 dest = os.path.join(base, wfn)
184 dest = os.path.join(base, wfn)
185
185
186 fnsandstat.append((dest, repo.wjoin(fn), os.lstat(dest)))
186 fnsandstat.append((dest, repo.wjoin(fn), os.lstat(dest)))
187 return dirname, fnsandstat
187 return dirname, fnsandstat
188
188
189
189
190 def formatcmdline(
190 def formatcmdline(
191 cmdline,
191 cmdline,
192 repo_root,
192 repo_root,
193 do3way,
193 do3way,
194 parent1,
194 parent1,
195 plabel1,
195 plabel1,
196 parent2,
196 parent2,
197 plabel2,
197 plabel2,
198 child,
198 child,
199 clabel,
199 clabel,
200 ):
200 ):
201 # Function to quote file/dir names in the argument string.
201 # Function to quote file/dir names in the argument string.
202 # When not operating in 3-way mode, an empty string is
202 # When not operating in 3-way mode, an empty string is
203 # returned for parent2
203 # returned for parent2
204 replace = {
204 replace = {
205 b'parent': parent1,
205 b'parent': parent1,
206 b'parent1': parent1,
206 b'parent1': parent1,
207 b'parent2': parent2,
207 b'parent2': parent2,
208 b'plabel1': plabel1,
208 b'plabel1': plabel1,
209 b'plabel2': plabel2,
209 b'plabel2': plabel2,
210 b'child': child,
210 b'child': child,
211 b'clabel': clabel,
211 b'clabel': clabel,
212 b'root': repo_root,
212 b'root': repo_root,
213 }
213 }
214
214
215 def quote(match):
215 def quote(match):
216 pre = match.group(2)
216 pre = match.group(2)
217 key = match.group(3)
217 key = match.group(3)
218 if not do3way and key == b'parent2':
218 if not do3way and key == b'parent2':
219 return pre
219 return pre
220 return pre + procutil.shellquote(replace[key])
220 return pre + procutil.shellquote(replace[key])
221
221
222 # Match parent2 first, so 'parent1?' will match both parent1 and parent
222 # Match parent2 first, so 'parent1?' will match both parent1 and parent
223 regex = (
223 regex = (
224 br'''(['"]?)([^\s'"$]*)'''
224 br'''(['"]?)([^\s'"$]*)'''
225 br'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1'
225 br'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1'
226 )
226 )
227 if not do3way and not re.search(regex, cmdline):
227 if not do3way and not re.search(regex, cmdline):
228 cmdline += b' $parent1 $child'
228 cmdline += b' $parent1 $child'
229 return re.sub(regex, quote, cmdline)
229 return re.sub(regex, quote, cmdline)
230
230
231
231
232 def _systembackground(cmd, environ=None, cwd=None):
232 def _systembackground(cmd, environ=None, cwd=None):
233 ''' like 'procutil.system', but returns the Popen object directly
233 ''' like 'procutil.system', but returns the Popen object directly
234 so we don't have to wait on it.
234 so we don't have to wait on it.
235 '''
235 '''
236 cmd = procutil.quotecommand(cmd)
236 cmd = procutil.quotecommand(cmd)
237 env = procutil.shellenviron(environ)
237 env = procutil.shellenviron(environ)
238 proc = subprocess.Popen(
238 proc = subprocess.Popen(
239 procutil.tonativestr(cmd),
239 procutil.tonativestr(cmd),
240 shell=True,
240 shell=True,
241 close_fds=procutil.closefds,
241 close_fds=procutil.closefds,
242 env=procutil.tonativeenv(env),
242 env=procutil.tonativeenv(env),
243 cwd=pycompat.rapply(procutil.tonativestr, cwd),
243 cwd=pycompat.rapply(procutil.tonativestr, cwd),
244 )
244 )
245 return proc
245 return proc
246
246
247
247
248 def _runperfilediff(
248 def _runperfilediff(
249 cmdline,
249 cmdline,
250 repo_root,
250 repo_root,
251 ui,
251 ui,
252 guitool,
252 guitool,
253 do3way,
253 do3way,
254 confirm,
254 confirm,
255 commonfiles,
255 commonfiles,
256 tmproot,
256 tmproot,
257 dir1a,
257 dir1a,
258 dir1b,
258 dir1b,
259 dir2root,
259 dir2root,
260 dir2,
260 dir2,
261 rev1a,
261 rev1a,
262 rev1b,
262 rev1b,
263 rev2,
263 rev2,
264 ):
264 ):
265 # Note that we need to sort the list of files because it was
265 # Note that we need to sort the list of files because it was
266 # built in an "unstable" way and it's annoying to get files in a
266 # built in an "unstable" way and it's annoying to get files in a
267 # random order, especially when "confirm" mode is enabled.
267 # random order, especially when "confirm" mode is enabled.
268 waitprocs = []
268 waitprocs = []
269 totalfiles = len(commonfiles)
269 totalfiles = len(commonfiles)
270 for idx, commonfile in enumerate(sorted(commonfiles)):
270 for idx, commonfile in enumerate(sorted(commonfiles)):
271 path1a = os.path.join(tmproot, dir1a, commonfile)
271 path1a = os.path.join(tmproot, dir1a, commonfile)
272 label1a = commonfile + rev1a
272 label1a = commonfile + rev1a
273 if not os.path.isfile(path1a):
273 if not os.path.isfile(path1a):
274 path1a = os.devnull
274 path1a = pycompat.osdevnull
275
275
276 path1b = b''
276 path1b = b''
277 label1b = b''
277 label1b = b''
278 if do3way:
278 if do3way:
279 path1b = os.path.join(tmproot, dir1b, commonfile)
279 path1b = os.path.join(tmproot, dir1b, commonfile)
280 label1b = commonfile + rev1b
280 label1b = commonfile + rev1b
281 if not os.path.isfile(path1b):
281 if not os.path.isfile(path1b):
282 path1b = os.devnull
282 path1b = pycompat.osdevnull
283
283
284 path2 = os.path.join(dir2root, dir2, commonfile)
284 path2 = os.path.join(dir2root, dir2, commonfile)
285 label2 = commonfile + rev2
285 label2 = commonfile + rev2
286
286
287 if confirm:
287 if confirm:
288 # Prompt before showing this diff
288 # Prompt before showing this diff
289 difffiles = _(b'diff %s (%d of %d)') % (
289 difffiles = _(b'diff %s (%d of %d)') % (
290 commonfile,
290 commonfile,
291 idx + 1,
291 idx + 1,
292 totalfiles,
292 totalfiles,
293 )
293 )
294 responses = _(
294 responses = _(
295 b'[Yns?]'
295 b'[Yns?]'
296 b'$$ &Yes, show diff'
296 b'$$ &Yes, show diff'
297 b'$$ &No, skip this diff'
297 b'$$ &No, skip this diff'
298 b'$$ &Skip remaining diffs'
298 b'$$ &Skip remaining diffs'
299 b'$$ &? (display help)'
299 b'$$ &? (display help)'
300 )
300 )
301 r = ui.promptchoice(b'%s %s' % (difffiles, responses))
301 r = ui.promptchoice(b'%s %s' % (difffiles, responses))
302 if r == 3: # ?
302 if r == 3: # ?
303 while r == 3:
303 while r == 3:
304 for c, t in ui.extractchoices(responses)[1]:
304 for c, t in ui.extractchoices(responses)[1]:
305 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
305 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
306 r = ui.promptchoice(b'%s %s' % (difffiles, responses))
306 r = ui.promptchoice(b'%s %s' % (difffiles, responses))
307 if r == 0: # yes
307 if r == 0: # yes
308 pass
308 pass
309 elif r == 1: # no
309 elif r == 1: # no
310 continue
310 continue
311 elif r == 2: # skip
311 elif r == 2: # skip
312 break
312 break
313
313
314 curcmdline = formatcmdline(
314 curcmdline = formatcmdline(
315 cmdline,
315 cmdline,
316 repo_root,
316 repo_root,
317 do3way=do3way,
317 do3way=do3way,
318 parent1=path1a,
318 parent1=path1a,
319 plabel1=label1a,
319 plabel1=label1a,
320 parent2=path1b,
320 parent2=path1b,
321 plabel2=label1b,
321 plabel2=label1b,
322 child=path2,
322 child=path2,
323 clabel=label2,
323 clabel=label2,
324 )
324 )
325
325
326 if confirm or not guitool:
326 if confirm or not guitool:
327 # Run the comparison program and wait for it to exit
327 # Run the comparison program and wait for it to exit
328 # before we show the next file.
328 # before we show the next file.
329 # This is because either we need to wait for confirmation
329 # This is because either we need to wait for confirmation
330 # from the user between each invocation, or because, as far
330 # from the user between each invocation, or because, as far
331 # as we know, the tool doesn't have a GUI, in which case
331 # as we know, the tool doesn't have a GUI, in which case
332 # we can't run multiple CLI programs at the same time.
332 # we can't run multiple CLI programs at the same time.
333 ui.debug(
333 ui.debug(
334 b'running %r in %s\n' % (pycompat.bytestr(curcmdline), tmproot)
334 b'running %r in %s\n' % (pycompat.bytestr(curcmdline), tmproot)
335 )
335 )
336 ui.system(curcmdline, cwd=tmproot, blockedtag=b'extdiff')
336 ui.system(curcmdline, cwd=tmproot, blockedtag=b'extdiff')
337 else:
337 else:
338 # Run the comparison program but don't wait, as we're
338 # Run the comparison program but don't wait, as we're
339 # going to rapid-fire each file diff and then wait on
339 # going to rapid-fire each file diff and then wait on
340 # the whole group.
340 # the whole group.
341 ui.debug(
341 ui.debug(
342 b'running %r in %s (backgrounded)\n'
342 b'running %r in %s (backgrounded)\n'
343 % (pycompat.bytestr(curcmdline), tmproot)
343 % (pycompat.bytestr(curcmdline), tmproot)
344 )
344 )
345 proc = _systembackground(curcmdline, cwd=tmproot)
345 proc = _systembackground(curcmdline, cwd=tmproot)
346 waitprocs.append(proc)
346 waitprocs.append(proc)
347
347
348 if waitprocs:
348 if waitprocs:
349 with ui.timeblockedsection(b'extdiff'):
349 with ui.timeblockedsection(b'extdiff'):
350 for proc in waitprocs:
350 for proc in waitprocs:
351 proc.wait()
351 proc.wait()
352
352
353
353
354 def dodiff(ui, repo, cmdline, pats, opts, guitool=False):
354 def dodiff(ui, repo, cmdline, pats, opts, guitool=False):
355 '''Do the actual diff:
355 '''Do the actual diff:
356
356
357 - copy to a temp structure if diffing 2 internal revisions
357 - copy to a temp structure if diffing 2 internal revisions
358 - copy to a temp structure if diffing working revision with
358 - copy to a temp structure if diffing working revision with
359 another one and more than 1 file is changed
359 another one and more than 1 file is changed
360 - just invoke the diff for a single file in the working dir
360 - just invoke the diff for a single file in the working dir
361 '''
361 '''
362
362
363 revs = opts.get(b'rev')
363 revs = opts.get(b'rev')
364 change = opts.get(b'change')
364 change = opts.get(b'change')
365 do3way = b'$parent2' in cmdline
365 do3way = b'$parent2' in cmdline
366
366
367 if revs and change:
367 if revs and change:
368 msg = _(b'cannot specify --rev and --change at the same time')
368 msg = _(b'cannot specify --rev and --change at the same time')
369 raise error.Abort(msg)
369 raise error.Abort(msg)
370 elif change:
370 elif change:
371 ctx2 = scmutil.revsingle(repo, change, None)
371 ctx2 = scmutil.revsingle(repo, change, None)
372 ctx1a, ctx1b = ctx2.p1(), ctx2.p2()
372 ctx1a, ctx1b = ctx2.p1(), ctx2.p2()
373 else:
373 else:
374 ctx1a, ctx2 = scmutil.revpair(repo, revs)
374 ctx1a, ctx2 = scmutil.revpair(repo, revs)
375 if not revs:
375 if not revs:
376 ctx1b = repo[None].p2()
376 ctx1b = repo[None].p2()
377 else:
377 else:
378 ctx1b = repo[nullid]
378 ctx1b = repo[nullid]
379
379
380 perfile = opts.get(b'per_file')
380 perfile = opts.get(b'per_file')
381 confirm = opts.get(b'confirm')
381 confirm = opts.get(b'confirm')
382
382
383 node1a = ctx1a.node()
383 node1a = ctx1a.node()
384 node1b = ctx1b.node()
384 node1b = ctx1b.node()
385 node2 = ctx2.node()
385 node2 = ctx2.node()
386
386
387 # Disable 3-way merge if there is only one parent
387 # Disable 3-way merge if there is only one parent
388 if do3way:
388 if do3way:
389 if node1b == nullid:
389 if node1b == nullid:
390 do3way = False
390 do3way = False
391
391
392 subrepos = opts.get(b'subrepos')
392 subrepos = opts.get(b'subrepos')
393
393
394 matcher = scmutil.match(repo[node2], pats, opts)
394 matcher = scmutil.match(repo[node2], pats, opts)
395
395
396 if opts.get(b'patch'):
396 if opts.get(b'patch'):
397 if subrepos:
397 if subrepos:
398 raise error.Abort(_(b'--patch cannot be used with --subrepos'))
398 raise error.Abort(_(b'--patch cannot be used with --subrepos'))
399 if perfile:
399 if perfile:
400 raise error.Abort(_(b'--patch cannot be used with --per-file'))
400 raise error.Abort(_(b'--patch cannot be used with --per-file'))
401 if node2 is None:
401 if node2 is None:
402 raise error.Abort(_(b'--patch requires two revisions'))
402 raise error.Abort(_(b'--patch requires two revisions'))
403 else:
403 else:
404 st = repo.status(node1a, node2, matcher, listsubrepos=subrepos)
404 st = repo.status(node1a, node2, matcher, listsubrepos=subrepos)
405 mod_a, add_a, rem_a = set(st.modified), set(st.added), set(st.removed)
405 mod_a, add_a, rem_a = set(st.modified), set(st.added), set(st.removed)
406 if do3way:
406 if do3way:
407 stb = repo.status(node1b, node2, matcher, listsubrepos=subrepos)
407 stb = repo.status(node1b, node2, matcher, listsubrepos=subrepos)
408 mod_b, add_b, rem_b = (
408 mod_b, add_b, rem_b = (
409 set(stb.modified),
409 set(stb.modified),
410 set(stb.added),
410 set(stb.added),
411 set(stb.removed),
411 set(stb.removed),
412 )
412 )
413 else:
413 else:
414 mod_b, add_b, rem_b = set(), set(), set()
414 mod_b, add_b, rem_b = set(), set(), set()
415 modadd = mod_a | add_a | mod_b | add_b
415 modadd = mod_a | add_a | mod_b | add_b
416 common = modadd | rem_a | rem_b
416 common = modadd | rem_a | rem_b
417 if not common:
417 if not common:
418 return 0
418 return 0
419
419
420 tmproot = pycompat.mkdtemp(prefix=b'extdiff.')
420 tmproot = pycompat.mkdtemp(prefix=b'extdiff.')
421 try:
421 try:
422 if not opts.get(b'patch'):
422 if not opts.get(b'patch'):
423 # Always make a copy of node1a (and node1b, if applicable)
423 # Always make a copy of node1a (and node1b, if applicable)
424 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
424 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
425 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot, subrepos)[
425 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot, subrepos)[
426 0
426 0
427 ]
427 ]
428 rev1a = b'@%d' % repo[node1a].rev()
428 rev1a = b'@%d' % repo[node1a].rev()
429 if do3way:
429 if do3way:
430 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
430 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
431 dir1b = snapshot(
431 dir1b = snapshot(
432 ui, repo, dir1b_files, node1b, tmproot, subrepos
432 ui, repo, dir1b_files, node1b, tmproot, subrepos
433 )[0]
433 )[0]
434 rev1b = b'@%d' % repo[node1b].rev()
434 rev1b = b'@%d' % repo[node1b].rev()
435 else:
435 else:
436 dir1b = None
436 dir1b = None
437 rev1b = b''
437 rev1b = b''
438
438
439 fnsandstat = []
439 fnsandstat = []
440
440
441 # If node2 in not the wc or there is >1 change, copy it
441 # If node2 in not the wc or there is >1 change, copy it
442 dir2root = b''
442 dir2root = b''
443 rev2 = b''
443 rev2 = b''
444 if node2:
444 if node2:
445 dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0]
445 dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0]
446 rev2 = b'@%d' % repo[node2].rev()
446 rev2 = b'@%d' % repo[node2].rev()
447 elif len(common) > 1:
447 elif len(common) > 1:
448 # we only actually need to get the files to copy back to
448 # we only actually need to get the files to copy back to
449 # the working dir in this case (because the other cases
449 # the working dir in this case (because the other cases
450 # are: diffing 2 revisions or single file -- in which case
450 # are: diffing 2 revisions or single file -- in which case
451 # the file is already directly passed to the diff tool).
451 # the file is already directly passed to the diff tool).
452 dir2, fnsandstat = snapshot(
452 dir2, fnsandstat = snapshot(
453 ui, repo, modadd, None, tmproot, subrepos
453 ui, repo, modadd, None, tmproot, subrepos
454 )
454 )
455 else:
455 else:
456 # This lets the diff tool open the changed file directly
456 # This lets the diff tool open the changed file directly
457 dir2 = b''
457 dir2 = b''
458 dir2root = repo.root
458 dir2root = repo.root
459
459
460 label1a = rev1a
460 label1a = rev1a
461 label1b = rev1b
461 label1b = rev1b
462 label2 = rev2
462 label2 = rev2
463
463
464 # If only one change, diff the files instead of the directories
464 # If only one change, diff the files instead of the directories
465 # Handle bogus modifies correctly by checking if the files exist
465 # Handle bogus modifies correctly by checking if the files exist
466 if len(common) == 1:
466 if len(common) == 1:
467 common_file = util.localpath(common.pop())
467 common_file = util.localpath(common.pop())
468 dir1a = os.path.join(tmproot, dir1a, common_file)
468 dir1a = os.path.join(tmproot, dir1a, common_file)
469 label1a = common_file + rev1a
469 label1a = common_file + rev1a
470 if not os.path.isfile(dir1a):
470 if not os.path.isfile(dir1a):
471 dir1a = os.devnull
471 dir1a = pycompat.osdevnull
472 if do3way:
472 if do3way:
473 dir1b = os.path.join(tmproot, dir1b, common_file)
473 dir1b = os.path.join(tmproot, dir1b, common_file)
474 label1b = common_file + rev1b
474 label1b = common_file + rev1b
475 if not os.path.isfile(dir1b):
475 if not os.path.isfile(dir1b):
476 dir1b = os.devnull
476 dir1b = pycompat.osdevnull
477 dir2 = os.path.join(dir2root, dir2, common_file)
477 dir2 = os.path.join(dir2root, dir2, common_file)
478 label2 = common_file + rev2
478 label2 = common_file + rev2
479 else:
479 else:
480 template = b'hg-%h.patch'
480 template = b'hg-%h.patch'
481 with formatter.nullformatter(ui, b'extdiff', {}) as fm:
481 with formatter.nullformatter(ui, b'extdiff', {}) as fm:
482 cmdutil.export(
482 cmdutil.export(
483 repo,
483 repo,
484 [repo[node1a].rev(), repo[node2].rev()],
484 [repo[node1a].rev(), repo[node2].rev()],
485 fm,
485 fm,
486 fntemplate=repo.vfs.reljoin(tmproot, template),
486 fntemplate=repo.vfs.reljoin(tmproot, template),
487 match=matcher,
487 match=matcher,
488 )
488 )
489 label1a = cmdutil.makefilename(repo[node1a], template)
489 label1a = cmdutil.makefilename(repo[node1a], template)
490 label2 = cmdutil.makefilename(repo[node2], template)
490 label2 = cmdutil.makefilename(repo[node2], template)
491 dir1a = repo.vfs.reljoin(tmproot, label1a)
491 dir1a = repo.vfs.reljoin(tmproot, label1a)
492 dir2 = repo.vfs.reljoin(tmproot, label2)
492 dir2 = repo.vfs.reljoin(tmproot, label2)
493 dir1b = None
493 dir1b = None
494 label1b = None
494 label1b = None
495 fnsandstat = []
495 fnsandstat = []
496
496
497 if not perfile:
497 if not perfile:
498 # Run the external tool on the 2 temp directories or the patches
498 # Run the external tool on the 2 temp directories or the patches
499 cmdline = formatcmdline(
499 cmdline = formatcmdline(
500 cmdline,
500 cmdline,
501 repo.root,
501 repo.root,
502 do3way=do3way,
502 do3way=do3way,
503 parent1=dir1a,
503 parent1=dir1a,
504 plabel1=label1a,
504 plabel1=label1a,
505 parent2=dir1b,
505 parent2=dir1b,
506 plabel2=label1b,
506 plabel2=label1b,
507 child=dir2,
507 child=dir2,
508 clabel=label2,
508 clabel=label2,
509 )
509 )
510 ui.debug(
510 ui.debug(
511 b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot)
511 b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot)
512 )
512 )
513 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff')
513 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff')
514 else:
514 else:
515 # Run the external tool once for each pair of files
515 # Run the external tool once for each pair of files
516 _runperfilediff(
516 _runperfilediff(
517 cmdline,
517 cmdline,
518 repo.root,
518 repo.root,
519 ui,
519 ui,
520 guitool=guitool,
520 guitool=guitool,
521 do3way=do3way,
521 do3way=do3way,
522 confirm=confirm,
522 confirm=confirm,
523 commonfiles=common,
523 commonfiles=common,
524 tmproot=tmproot,
524 tmproot=tmproot,
525 dir1a=dir1a,
525 dir1a=dir1a,
526 dir1b=dir1b,
526 dir1b=dir1b,
527 dir2root=dir2root,
527 dir2root=dir2root,
528 dir2=dir2,
528 dir2=dir2,
529 rev1a=rev1a,
529 rev1a=rev1a,
530 rev1b=rev1b,
530 rev1b=rev1b,
531 rev2=rev2,
531 rev2=rev2,
532 )
532 )
533
533
534 for copy_fn, working_fn, st in fnsandstat:
534 for copy_fn, working_fn, st in fnsandstat:
535 cpstat = os.lstat(copy_fn)
535 cpstat = os.lstat(copy_fn)
536 # Some tools copy the file and attributes, so mtime may not detect
536 # Some tools copy the file and attributes, so mtime may not detect
537 # all changes. A size check will detect more cases, but not all.
537 # all changes. A size check will detect more cases, but not all.
538 # The only certain way to detect every case is to diff all files,
538 # The only certain way to detect every case is to diff all files,
539 # which could be expensive.
539 # which could be expensive.
540 # copyfile() carries over the permission, so the mode check could
540 # copyfile() carries over the permission, so the mode check could
541 # be in an 'elif' branch, but for the case where the file has
541 # be in an 'elif' branch, but for the case where the file has
542 # changed without affecting mtime or size.
542 # changed without affecting mtime or size.
543 if (
543 if (
544 cpstat[stat.ST_MTIME] != st[stat.ST_MTIME]
544 cpstat[stat.ST_MTIME] != st[stat.ST_MTIME]
545 or cpstat.st_size != st.st_size
545 or cpstat.st_size != st.st_size
546 or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)
546 or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)
547 ):
547 ):
548 ui.debug(
548 ui.debug(
549 b'file changed while diffing. '
549 b'file changed while diffing. '
550 b'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)
550 b'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)
551 )
551 )
552 util.copyfile(copy_fn, working_fn)
552 util.copyfile(copy_fn, working_fn)
553
553
554 return 1
554 return 1
555 finally:
555 finally:
556 ui.note(_(b'cleaning up temp directory\n'))
556 ui.note(_(b'cleaning up temp directory\n'))
557 shutil.rmtree(tmproot)
557 shutil.rmtree(tmproot)
558
558
559
559
560 extdiffopts = (
560 extdiffopts = (
561 [
561 [
562 (
562 (
563 b'o',
563 b'o',
564 b'option',
564 b'option',
565 [],
565 [],
566 _(b'pass option to comparison program'),
566 _(b'pass option to comparison program'),
567 _(b'OPT'),
567 _(b'OPT'),
568 ),
568 ),
569 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
569 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
570 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
570 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
571 (
571 (
572 b'',
572 b'',
573 b'per-file',
573 b'per-file',
574 False,
574 False,
575 _(b'compare each file instead of revision snapshots'),
575 _(b'compare each file instead of revision snapshots'),
576 ),
576 ),
577 (
577 (
578 b'',
578 b'',
579 b'confirm',
579 b'confirm',
580 False,
580 False,
581 _(b'prompt user before each external program invocation'),
581 _(b'prompt user before each external program invocation'),
582 ),
582 ),
583 (b'', b'patch', None, _(b'compare patches for two revisions')),
583 (b'', b'patch', None, _(b'compare patches for two revisions')),
584 ]
584 ]
585 + cmdutil.walkopts
585 + cmdutil.walkopts
586 + cmdutil.subrepoopts
586 + cmdutil.subrepoopts
587 )
587 )
588
588
589
589
590 @command(
590 @command(
591 b'extdiff',
591 b'extdiff',
592 [(b'p', b'program', b'', _(b'comparison program to run'), _(b'CMD')),]
592 [(b'p', b'program', b'', _(b'comparison program to run'), _(b'CMD')),]
593 + extdiffopts,
593 + extdiffopts,
594 _(b'hg extdiff [OPT]... [FILE]...'),
594 _(b'hg extdiff [OPT]... [FILE]...'),
595 helpcategory=command.CATEGORY_FILE_CONTENTS,
595 helpcategory=command.CATEGORY_FILE_CONTENTS,
596 inferrepo=True,
596 inferrepo=True,
597 )
597 )
598 def extdiff(ui, repo, *pats, **opts):
598 def extdiff(ui, repo, *pats, **opts):
599 '''use external program to diff repository (or selected files)
599 '''use external program to diff repository (or selected files)
600
600
601 Show differences between revisions for the specified files, using
601 Show differences between revisions for the specified files, using
602 an external program. The default program used is diff, with
602 an external program. The default program used is diff, with
603 default options "-Npru".
603 default options "-Npru".
604
604
605 To select a different program, use the -p/--program option. The
605 To select a different program, use the -p/--program option. The
606 program will be passed the names of two directories to compare,
606 program will be passed the names of two directories to compare,
607 unless the --per-file option is specified (see below). To pass
607 unless the --per-file option is specified (see below). To pass
608 additional options to the program, use -o/--option. These will be
608 additional options to the program, use -o/--option. These will be
609 passed before the names of the directories or files to compare.
609 passed before the names of the directories or files to compare.
610
610
611 When two revision arguments are given, then changes are shown
611 When two revision arguments are given, then changes are shown
612 between those revisions. If only one revision is specified then
612 between those revisions. If only one revision is specified then
613 that revision is compared to the working directory, and, when no
613 that revision is compared to the working directory, and, when no
614 revisions are specified, the working directory files are compared
614 revisions are specified, the working directory files are compared
615 to its parent.
615 to its parent.
616
616
617 The --per-file option runs the external program repeatedly on each
617 The --per-file option runs the external program repeatedly on each
618 file to diff, instead of once on two directories. By default,
618 file to diff, instead of once on two directories. By default,
619 this happens one by one, where the next file diff is open in the
619 this happens one by one, where the next file diff is open in the
620 external program only once the previous external program (for the
620 external program only once the previous external program (for the
621 previous file diff) has exited. If the external program has a
621 previous file diff) has exited. If the external program has a
622 graphical interface, it can open all the file diffs at once instead
622 graphical interface, it can open all the file diffs at once instead
623 of one by one. See :hg:`help -e extdiff` for information about how
623 of one by one. See :hg:`help -e extdiff` for information about how
624 to tell Mercurial that a given program has a graphical interface.
624 to tell Mercurial that a given program has a graphical interface.
625
625
626 The --confirm option will prompt the user before each invocation of
626 The --confirm option will prompt the user before each invocation of
627 the external program. It is ignored if --per-file isn't specified.
627 the external program. It is ignored if --per-file isn't specified.
628 '''
628 '''
629 opts = pycompat.byteskwargs(opts)
629 opts = pycompat.byteskwargs(opts)
630 program = opts.get(b'program')
630 program = opts.get(b'program')
631 option = opts.get(b'option')
631 option = opts.get(b'option')
632 if not program:
632 if not program:
633 program = b'diff'
633 program = b'diff'
634 option = option or [b'-Npru']
634 option = option or [b'-Npru']
635 cmdline = b' '.join(map(procutil.shellquote, [program] + option))
635 cmdline = b' '.join(map(procutil.shellquote, [program] + option))
636 return dodiff(ui, repo, cmdline, pats, opts)
636 return dodiff(ui, repo, cmdline, pats, opts)
637
637
638
638
639 class savedcmd(object):
639 class savedcmd(object):
640 """use external program to diff repository (or selected files)
640 """use external program to diff repository (or selected files)
641
641
642 Show differences between revisions for the specified files, using
642 Show differences between revisions for the specified files, using
643 the following program::
643 the following program::
644
644
645 %(path)s
645 %(path)s
646
646
647 When two revision arguments are given, then changes are shown
647 When two revision arguments are given, then changes are shown
648 between those revisions. If only one revision is specified then
648 between those revisions. If only one revision is specified then
649 that revision is compared to the working directory, and, when no
649 that revision is compared to the working directory, and, when no
650 revisions are specified, the working directory files are compared
650 revisions are specified, the working directory files are compared
651 to its parent.
651 to its parent.
652 """
652 """
653
653
654 def __init__(self, path, cmdline, isgui):
654 def __init__(self, path, cmdline, isgui):
655 # We can't pass non-ASCII through docstrings (and path is
655 # We can't pass non-ASCII through docstrings (and path is
656 # in an unknown encoding anyway), but avoid double separators on
656 # in an unknown encoding anyway), but avoid double separators on
657 # Windows
657 # Windows
658 docpath = stringutil.escapestr(path).replace(b'\\\\', b'\\')
658 docpath = stringutil.escapestr(path).replace(b'\\\\', b'\\')
659 self.__doc__ %= {'path': pycompat.sysstr(stringutil.uirepr(docpath))}
659 self.__doc__ %= {'path': pycompat.sysstr(stringutil.uirepr(docpath))}
660 self._cmdline = cmdline
660 self._cmdline = cmdline
661 self._isgui = isgui
661 self._isgui = isgui
662
662
663 def __call__(self, ui, repo, *pats, **opts):
663 def __call__(self, ui, repo, *pats, **opts):
664 opts = pycompat.byteskwargs(opts)
664 opts = pycompat.byteskwargs(opts)
665 options = b' '.join(map(procutil.shellquote, opts[b'option']))
665 options = b' '.join(map(procutil.shellquote, opts[b'option']))
666 if options:
666 if options:
667 options = b' ' + options
667 options = b' ' + options
668 return dodiff(
668 return dodiff(
669 ui, repo, self._cmdline + options, pats, opts, guitool=self._isgui
669 ui, repo, self._cmdline + options, pats, opts, guitool=self._isgui
670 )
670 )
671
671
672
672
673 def uisetup(ui):
673 def uisetup(ui):
674 for cmd, path in ui.configitems(b'extdiff'):
674 for cmd, path in ui.configitems(b'extdiff'):
675 path = util.expandpath(path)
675 path = util.expandpath(path)
676 if cmd.startswith(b'cmd.'):
676 if cmd.startswith(b'cmd.'):
677 cmd = cmd[4:]
677 cmd = cmd[4:]
678 if not path:
678 if not path:
679 path = procutil.findexe(cmd)
679 path = procutil.findexe(cmd)
680 if path is None:
680 if path is None:
681 path = filemerge.findexternaltool(ui, cmd) or cmd
681 path = filemerge.findexternaltool(ui, cmd) or cmd
682 diffopts = ui.config(b'extdiff', b'opts.' + cmd)
682 diffopts = ui.config(b'extdiff', b'opts.' + cmd)
683 cmdline = procutil.shellquote(path)
683 cmdline = procutil.shellquote(path)
684 if diffopts:
684 if diffopts:
685 cmdline += b' ' + diffopts
685 cmdline += b' ' + diffopts
686 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
686 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
687 elif cmd.startswith(b'opts.') or cmd.startswith(b'gui.'):
687 elif cmd.startswith(b'opts.') or cmd.startswith(b'gui.'):
688 continue
688 continue
689 else:
689 else:
690 if path:
690 if path:
691 # case "cmd = path opts"
691 # case "cmd = path opts"
692 cmdline = path
692 cmdline = path
693 diffopts = len(pycompat.shlexsplit(cmdline)) > 1
693 diffopts = len(pycompat.shlexsplit(cmdline)) > 1
694 else:
694 else:
695 # case "cmd ="
695 # case "cmd ="
696 path = procutil.findexe(cmd)
696 path = procutil.findexe(cmd)
697 if path is None:
697 if path is None:
698 path = filemerge.findexternaltool(ui, cmd) or cmd
698 path = filemerge.findexternaltool(ui, cmd) or cmd
699 cmdline = procutil.shellquote(path)
699 cmdline = procutil.shellquote(path)
700 diffopts = False
700 diffopts = False
701 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
701 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
702 # look for diff arguments in [diff-tools] then [merge-tools]
702 # look for diff arguments in [diff-tools] then [merge-tools]
703 if not diffopts:
703 if not diffopts:
704 key = cmd + b'.diffargs'
704 key = cmd + b'.diffargs'
705 for section in (b'diff-tools', b'merge-tools'):
705 for section in (b'diff-tools', b'merge-tools'):
706 args = ui.config(section, key)
706 args = ui.config(section, key)
707 if args:
707 if args:
708 cmdline += b' ' + args
708 cmdline += b' ' + args
709 if isgui is None:
709 if isgui is None:
710 isgui = ui.configbool(section, cmd + b'.gui') or False
710 isgui = ui.configbool(section, cmd + b'.gui') or False
711 break
711 break
712 command(
712 command(
713 cmd,
713 cmd,
714 extdiffopts[:],
714 extdiffopts[:],
715 _(b'hg %s [OPTION]... [FILE]...') % cmd,
715 _(b'hg %s [OPTION]... [FILE]...') % cmd,
716 helpcategory=command.CATEGORY_FILE_CONTENTS,
716 helpcategory=command.CATEGORY_FILE_CONTENTS,
717 inferrepo=True,
717 inferrepo=True,
718 )(savedcmd(path, cmdline, isgui))
718 )(savedcmd(path, cmdline, isgui))
719
719
720
720
721 # tell hggettext to extract docstrings from these functions:
721 # tell hggettext to extract docstrings from these functions:
722 i18nfunctions = [savedcmd]
722 i18nfunctions = [savedcmd]
@@ -1,512 +1,514 b''
1 # pycompat.py - portability shim for python 3
1 # pycompat.py - portability shim for python 3
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 """Mercurial portability shim for python 3.
6 """Mercurial portability shim for python 3.
7
7
8 This contains aliases to hide python version-specific details from the core.
8 This contains aliases to hide python version-specific details from the core.
9 """
9 """
10
10
11 from __future__ import absolute_import
11 from __future__ import absolute_import
12
12
13 import getopt
13 import getopt
14 import inspect
14 import inspect
15 import json
15 import json
16 import os
16 import os
17 import shlex
17 import shlex
18 import sys
18 import sys
19 import tempfile
19 import tempfile
20
20
21 ispy3 = sys.version_info[0] >= 3
21 ispy3 = sys.version_info[0] >= 3
22 ispypy = '__pypy__' in sys.builtin_module_names
22 ispypy = '__pypy__' in sys.builtin_module_names
23 TYPE_CHECKING = False
23 TYPE_CHECKING = False
24
24
25 if not globals(): # hide this from non-pytype users
25 if not globals(): # hide this from non-pytype users
26 import typing
26 import typing
27
27
28 TYPE_CHECKING = typing.TYPE_CHECKING
28 TYPE_CHECKING = typing.TYPE_CHECKING
29
29
30 if not ispy3:
30 if not ispy3:
31 import cookielib
31 import cookielib
32 import cPickle as pickle
32 import cPickle as pickle
33 import httplib
33 import httplib
34 import Queue as queue
34 import Queue as queue
35 import SocketServer as socketserver
35 import SocketServer as socketserver
36 import xmlrpclib
36 import xmlrpclib
37
37
38 from .thirdparty.concurrent import futures
38 from .thirdparty.concurrent import futures
39
39
40 def future_set_exception_info(f, exc_info):
40 def future_set_exception_info(f, exc_info):
41 f.set_exception_info(*exc_info)
41 f.set_exception_info(*exc_info)
42
42
43
43
44 else:
44 else:
45 import concurrent.futures as futures
45 import concurrent.futures as futures
46 import http.cookiejar as cookielib
46 import http.cookiejar as cookielib
47 import http.client as httplib
47 import http.client as httplib
48 import pickle
48 import pickle
49 import queue as queue
49 import queue as queue
50 import socketserver
50 import socketserver
51 import xmlrpc.client as xmlrpclib
51 import xmlrpc.client as xmlrpclib
52
52
53 def future_set_exception_info(f, exc_info):
53 def future_set_exception_info(f, exc_info):
54 f.set_exception(exc_info[0])
54 f.set_exception(exc_info[0])
55
55
56
56
57 def identity(a):
57 def identity(a):
58 return a
58 return a
59
59
60
60
61 def _rapply(f, xs):
61 def _rapply(f, xs):
62 if xs is None:
62 if xs is None:
63 # assume None means non-value of optional data
63 # assume None means non-value of optional data
64 return xs
64 return xs
65 if isinstance(xs, (list, set, tuple)):
65 if isinstance(xs, (list, set, tuple)):
66 return type(xs)(_rapply(f, x) for x in xs)
66 return type(xs)(_rapply(f, x) for x in xs)
67 if isinstance(xs, dict):
67 if isinstance(xs, dict):
68 return type(xs)((_rapply(f, k), _rapply(f, v)) for k, v in xs.items())
68 return type(xs)((_rapply(f, k), _rapply(f, v)) for k, v in xs.items())
69 return f(xs)
69 return f(xs)
70
70
71
71
72 def rapply(f, xs):
72 def rapply(f, xs):
73 """Apply function recursively to every item preserving the data structure
73 """Apply function recursively to every item preserving the data structure
74
74
75 >>> def f(x):
75 >>> def f(x):
76 ... return 'f(%s)' % x
76 ... return 'f(%s)' % x
77 >>> rapply(f, None) is None
77 >>> rapply(f, None) is None
78 True
78 True
79 >>> rapply(f, 'a')
79 >>> rapply(f, 'a')
80 'f(a)'
80 'f(a)'
81 >>> rapply(f, {'a'}) == {'f(a)'}
81 >>> rapply(f, {'a'}) == {'f(a)'}
82 True
82 True
83 >>> rapply(f, ['a', 'b', None, {'c': 'd'}, []])
83 >>> rapply(f, ['a', 'b', None, {'c': 'd'}, []])
84 ['f(a)', 'f(b)', None, {'f(c)': 'f(d)'}, []]
84 ['f(a)', 'f(b)', None, {'f(c)': 'f(d)'}, []]
85
85
86 >>> xs = [object()]
86 >>> xs = [object()]
87 >>> rapply(identity, xs) is xs
87 >>> rapply(identity, xs) is xs
88 True
88 True
89 """
89 """
90 if f is identity:
90 if f is identity:
91 # fast path mainly for py2
91 # fast path mainly for py2
92 return xs
92 return xs
93 return _rapply(f, xs)
93 return _rapply(f, xs)
94
94
95
95
96 if ispy3:
96 if ispy3:
97 import builtins
97 import builtins
98 import codecs
98 import codecs
99 import functools
99 import functools
100 import io
100 import io
101 import struct
101 import struct
102
102
103 if os.name == r'nt' and sys.version_info >= (3, 6):
103 if os.name == r'nt' and sys.version_info >= (3, 6):
104 # MBCS (or ANSI) filesystem encoding must be used as before.
104 # MBCS (or ANSI) filesystem encoding must be used as before.
105 # Otherwise non-ASCII filenames in existing repositories would be
105 # Otherwise non-ASCII filenames in existing repositories would be
106 # corrupted.
106 # corrupted.
107 # This must be set once prior to any fsencode/fsdecode calls.
107 # This must be set once prior to any fsencode/fsdecode calls.
108 sys._enablelegacywindowsfsencoding() # pytype: disable=module-attr
108 sys._enablelegacywindowsfsencoding() # pytype: disable=module-attr
109
109
110 fsencode = os.fsencode
110 fsencode = os.fsencode
111 fsdecode = os.fsdecode
111 fsdecode = os.fsdecode
112 oscurdir = os.curdir.encode('ascii')
112 oscurdir = os.curdir.encode('ascii')
113 oslinesep = os.linesep.encode('ascii')
113 oslinesep = os.linesep.encode('ascii')
114 osname = os.name.encode('ascii')
114 osname = os.name.encode('ascii')
115 ospathsep = os.pathsep.encode('ascii')
115 ospathsep = os.pathsep.encode('ascii')
116 ospardir = os.pardir.encode('ascii')
116 ospardir = os.pardir.encode('ascii')
117 ossep = os.sep.encode('ascii')
117 ossep = os.sep.encode('ascii')
118 osaltsep = os.altsep
118 osaltsep = os.altsep
119 if osaltsep:
119 if osaltsep:
120 osaltsep = osaltsep.encode('ascii')
120 osaltsep = osaltsep.encode('ascii')
121 osdevnull = os.devnull.encode('ascii')
121
122
122 sysplatform = sys.platform.encode('ascii')
123 sysplatform = sys.platform.encode('ascii')
123 sysexecutable = sys.executable
124 sysexecutable = sys.executable
124 if sysexecutable:
125 if sysexecutable:
125 sysexecutable = os.fsencode(sysexecutable)
126 sysexecutable = os.fsencode(sysexecutable)
126 bytesio = io.BytesIO
127 bytesio = io.BytesIO
127 # TODO deprecate stringio name, as it is a lie on Python 3.
128 # TODO deprecate stringio name, as it is a lie on Python 3.
128 stringio = bytesio
129 stringio = bytesio
129
130
130 def maplist(*args):
131 def maplist(*args):
131 return list(map(*args))
132 return list(map(*args))
132
133
133 def rangelist(*args):
134 def rangelist(*args):
134 return list(range(*args))
135 return list(range(*args))
135
136
136 def ziplist(*args):
137 def ziplist(*args):
137 return list(zip(*args))
138 return list(zip(*args))
138
139
139 rawinput = input
140 rawinput = input
140 getargspec = inspect.getfullargspec
141 getargspec = inspect.getfullargspec
141
142
142 long = int
143 long = int
143
144
144 # TODO: .buffer might not exist if std streams were replaced; we'll need
145 # TODO: .buffer might not exist if std streams were replaced; we'll need
145 # a silly wrapper to make a bytes stream backed by a unicode one.
146 # a silly wrapper to make a bytes stream backed by a unicode one.
146 stdin = sys.stdin.buffer
147 stdin = sys.stdin.buffer
147 stdout = sys.stdout.buffer
148 stdout = sys.stdout.buffer
148 stderr = sys.stderr.buffer
149 stderr = sys.stderr.buffer
149
150
150 # Since Python 3 converts argv to wchar_t type by Py_DecodeLocale() on Unix,
151 # Since Python 3 converts argv to wchar_t type by Py_DecodeLocale() on Unix,
151 # we can use os.fsencode() to get back bytes argv.
152 # we can use os.fsencode() to get back bytes argv.
152 #
153 #
153 # https://hg.python.org/cpython/file/v3.5.1/Programs/python.c#l55
154 # https://hg.python.org/cpython/file/v3.5.1/Programs/python.c#l55
154 #
155 #
155 # On Windows, the native argv is unicode and is converted to MBCS bytes
156 # On Windows, the native argv is unicode and is converted to MBCS bytes
156 # since we do enable the legacy filesystem encoding.
157 # since we do enable the legacy filesystem encoding.
157 if getattr(sys, 'argv', None) is not None:
158 if getattr(sys, 'argv', None) is not None:
158 sysargv = list(map(os.fsencode, sys.argv))
159 sysargv = list(map(os.fsencode, sys.argv))
159
160
160 bytechr = struct.Struct('>B').pack
161 bytechr = struct.Struct('>B').pack
161 byterepr = b'%r'.__mod__
162 byterepr = b'%r'.__mod__
162
163
163 class bytestr(bytes):
164 class bytestr(bytes):
164 """A bytes which mostly acts as a Python 2 str
165 """A bytes which mostly acts as a Python 2 str
165
166
166 >>> bytestr(), bytestr(bytearray(b'foo')), bytestr(u'ascii'), bytestr(1)
167 >>> bytestr(), bytestr(bytearray(b'foo')), bytestr(u'ascii'), bytestr(1)
167 ('', 'foo', 'ascii', '1')
168 ('', 'foo', 'ascii', '1')
168 >>> s = bytestr(b'foo')
169 >>> s = bytestr(b'foo')
169 >>> assert s is bytestr(s)
170 >>> assert s is bytestr(s)
170
171
171 __bytes__() should be called if provided:
172 __bytes__() should be called if provided:
172
173
173 >>> class bytesable(object):
174 >>> class bytesable(object):
174 ... def __bytes__(self):
175 ... def __bytes__(self):
175 ... return b'bytes'
176 ... return b'bytes'
176 >>> bytestr(bytesable())
177 >>> bytestr(bytesable())
177 'bytes'
178 'bytes'
178
179
179 There's no implicit conversion from non-ascii str as its encoding is
180 There's no implicit conversion from non-ascii str as its encoding is
180 unknown:
181 unknown:
181
182
182 >>> bytestr(chr(0x80)) # doctest: +ELLIPSIS
183 >>> bytestr(chr(0x80)) # doctest: +ELLIPSIS
183 Traceback (most recent call last):
184 Traceback (most recent call last):
184 ...
185 ...
185 UnicodeEncodeError: ...
186 UnicodeEncodeError: ...
186
187
187 Comparison between bytestr and bytes should work:
188 Comparison between bytestr and bytes should work:
188
189
189 >>> assert bytestr(b'foo') == b'foo'
190 >>> assert bytestr(b'foo') == b'foo'
190 >>> assert b'foo' == bytestr(b'foo')
191 >>> assert b'foo' == bytestr(b'foo')
191 >>> assert b'f' in bytestr(b'foo')
192 >>> assert b'f' in bytestr(b'foo')
192 >>> assert bytestr(b'f') in b'foo'
193 >>> assert bytestr(b'f') in b'foo'
193
194
194 Sliced elements should be bytes, not integer:
195 Sliced elements should be bytes, not integer:
195
196
196 >>> s[1], s[:2]
197 >>> s[1], s[:2]
197 (b'o', b'fo')
198 (b'o', b'fo')
198 >>> list(s), list(reversed(s))
199 >>> list(s), list(reversed(s))
199 ([b'f', b'o', b'o'], [b'o', b'o', b'f'])
200 ([b'f', b'o', b'o'], [b'o', b'o', b'f'])
200
201
201 As bytestr type isn't propagated across operations, you need to cast
202 As bytestr type isn't propagated across operations, you need to cast
202 bytes to bytestr explicitly:
203 bytes to bytestr explicitly:
203
204
204 >>> s = bytestr(b'foo').upper()
205 >>> s = bytestr(b'foo').upper()
205 >>> t = bytestr(s)
206 >>> t = bytestr(s)
206 >>> s[0], t[0]
207 >>> s[0], t[0]
207 (70, b'F')
208 (70, b'F')
208
209
209 Be careful to not pass a bytestr object to a function which expects
210 Be careful to not pass a bytestr object to a function which expects
210 bytearray-like behavior.
211 bytearray-like behavior.
211
212
212 >>> t = bytes(t) # cast to bytes
213 >>> t = bytes(t) # cast to bytes
213 >>> assert type(t) is bytes
214 >>> assert type(t) is bytes
214 """
215 """
215
216
216 def __new__(cls, s=b''):
217 def __new__(cls, s=b''):
217 if isinstance(s, bytestr):
218 if isinstance(s, bytestr):
218 return s
219 return s
219 if not isinstance(
220 if not isinstance(
220 s, (bytes, bytearray)
221 s, (bytes, bytearray)
221 ) and not hasattr( # hasattr-py3-only
222 ) and not hasattr( # hasattr-py3-only
222 s, u'__bytes__'
223 s, u'__bytes__'
223 ):
224 ):
224 s = str(s).encode('ascii')
225 s = str(s).encode('ascii')
225 return bytes.__new__(cls, s)
226 return bytes.__new__(cls, s)
226
227
227 def __getitem__(self, key):
228 def __getitem__(self, key):
228 s = bytes.__getitem__(self, key)
229 s = bytes.__getitem__(self, key)
229 if not isinstance(s, bytes):
230 if not isinstance(s, bytes):
230 s = bytechr(s)
231 s = bytechr(s)
231 return s
232 return s
232
233
233 def __iter__(self):
234 def __iter__(self):
234 return iterbytestr(bytes.__iter__(self))
235 return iterbytestr(bytes.__iter__(self))
235
236
236 def __repr__(self):
237 def __repr__(self):
237 return bytes.__repr__(self)[1:] # drop b''
238 return bytes.__repr__(self)[1:] # drop b''
238
239
239 def iterbytestr(s):
240 def iterbytestr(s):
240 """Iterate bytes as if it were a str object of Python 2"""
241 """Iterate bytes as if it were a str object of Python 2"""
241 return map(bytechr, s)
242 return map(bytechr, s)
242
243
243 def maybebytestr(s):
244 def maybebytestr(s):
244 """Promote bytes to bytestr"""
245 """Promote bytes to bytestr"""
245 if isinstance(s, bytes):
246 if isinstance(s, bytes):
246 return bytestr(s)
247 return bytestr(s)
247 return s
248 return s
248
249
249 def sysbytes(s):
250 def sysbytes(s):
250 """Convert an internal str (e.g. keyword, __doc__) back to bytes
251 """Convert an internal str (e.g. keyword, __doc__) back to bytes
251
252
252 This never raises UnicodeEncodeError, but only ASCII characters
253 This never raises UnicodeEncodeError, but only ASCII characters
253 can be round-trip by sysstr(sysbytes(s)).
254 can be round-trip by sysstr(sysbytes(s)).
254 """
255 """
255 return s.encode('utf-8')
256 return s.encode('utf-8')
256
257
257 def sysstr(s):
258 def sysstr(s):
258 """Return a keyword str to be passed to Python functions such as
259 """Return a keyword str to be passed to Python functions such as
259 getattr() and str.encode()
260 getattr() and str.encode()
260
261
261 This never raises UnicodeDecodeError. Non-ascii characters are
262 This never raises UnicodeDecodeError. Non-ascii characters are
262 considered invalid and mapped to arbitrary but unique code points
263 considered invalid and mapped to arbitrary but unique code points
263 such that 'sysstr(a) != sysstr(b)' for all 'a != b'.
264 such that 'sysstr(a) != sysstr(b)' for all 'a != b'.
264 """
265 """
265 if isinstance(s, builtins.str):
266 if isinstance(s, builtins.str):
266 return s
267 return s
267 return s.decode('latin-1')
268 return s.decode('latin-1')
268
269
269 def strurl(url):
270 def strurl(url):
270 """Converts a bytes url back to str"""
271 """Converts a bytes url back to str"""
271 if isinstance(url, bytes):
272 if isinstance(url, bytes):
272 return url.decode('ascii')
273 return url.decode('ascii')
273 return url
274 return url
274
275
275 def bytesurl(url):
276 def bytesurl(url):
276 """Converts a str url to bytes by encoding in ascii"""
277 """Converts a str url to bytes by encoding in ascii"""
277 if isinstance(url, str):
278 if isinstance(url, str):
278 return url.encode('ascii')
279 return url.encode('ascii')
279 return url
280 return url
280
281
281 def raisewithtb(exc, tb):
282 def raisewithtb(exc, tb):
282 """Raise exception with the given traceback"""
283 """Raise exception with the given traceback"""
283 raise exc.with_traceback(tb)
284 raise exc.with_traceback(tb)
284
285
285 def getdoc(obj):
286 def getdoc(obj):
286 """Get docstring as bytes; may be None so gettext() won't confuse it
287 """Get docstring as bytes; may be None so gettext() won't confuse it
287 with _('')"""
288 with _('')"""
288 doc = getattr(obj, '__doc__', None)
289 doc = getattr(obj, '__doc__', None)
289 if doc is None:
290 if doc is None:
290 return doc
291 return doc
291 return sysbytes(doc)
292 return sysbytes(doc)
292
293
293 def _wrapattrfunc(f):
294 def _wrapattrfunc(f):
294 @functools.wraps(f)
295 @functools.wraps(f)
295 def w(object, name, *args):
296 def w(object, name, *args):
296 return f(object, sysstr(name), *args)
297 return f(object, sysstr(name), *args)
297
298
298 return w
299 return w
299
300
300 # these wrappers are automagically imported by hgloader
301 # these wrappers are automagically imported by hgloader
301 delattr = _wrapattrfunc(builtins.delattr)
302 delattr = _wrapattrfunc(builtins.delattr)
302 getattr = _wrapattrfunc(builtins.getattr)
303 getattr = _wrapattrfunc(builtins.getattr)
303 hasattr = _wrapattrfunc(builtins.hasattr)
304 hasattr = _wrapattrfunc(builtins.hasattr)
304 setattr = _wrapattrfunc(builtins.setattr)
305 setattr = _wrapattrfunc(builtins.setattr)
305 xrange = builtins.range
306 xrange = builtins.range
306 unicode = str
307 unicode = str
307
308
308 def open(name, mode=b'r', buffering=-1, encoding=None):
309 def open(name, mode=b'r', buffering=-1, encoding=None):
309 return builtins.open(name, sysstr(mode), buffering, encoding)
310 return builtins.open(name, sysstr(mode), buffering, encoding)
310
311
311 safehasattr = _wrapattrfunc(builtins.hasattr)
312 safehasattr = _wrapattrfunc(builtins.hasattr)
312
313
313 def _getoptbwrapper(orig, args, shortlist, namelist):
314 def _getoptbwrapper(orig, args, shortlist, namelist):
314 """
315 """
315 Takes bytes arguments, converts them to unicode, pass them to
316 Takes bytes arguments, converts them to unicode, pass them to
316 getopt.getopt(), convert the returned values back to bytes and then
317 getopt.getopt(), convert the returned values back to bytes and then
317 return them for Python 3 compatibility as getopt.getopt() don't accepts
318 return them for Python 3 compatibility as getopt.getopt() don't accepts
318 bytes on Python 3.
319 bytes on Python 3.
319 """
320 """
320 args = [a.decode('latin-1') for a in args]
321 args = [a.decode('latin-1') for a in args]
321 shortlist = shortlist.decode('latin-1')
322 shortlist = shortlist.decode('latin-1')
322 namelist = [a.decode('latin-1') for a in namelist]
323 namelist = [a.decode('latin-1') for a in namelist]
323 opts, args = orig(args, shortlist, namelist)
324 opts, args = orig(args, shortlist, namelist)
324 opts = [(a[0].encode('latin-1'), a[1].encode('latin-1')) for a in opts]
325 opts = [(a[0].encode('latin-1'), a[1].encode('latin-1')) for a in opts]
325 args = [a.encode('latin-1') for a in args]
326 args = [a.encode('latin-1') for a in args]
326 return opts, args
327 return opts, args
327
328
328 def strkwargs(dic):
329 def strkwargs(dic):
329 """
330 """
330 Converts the keys of a python dictonary to str i.e. unicodes so that
331 Converts the keys of a python dictonary to str i.e. unicodes so that
331 they can be passed as keyword arguments as dictonaries with bytes keys
332 they can be passed as keyword arguments as dictonaries with bytes keys
332 can't be passed as keyword arguments to functions on Python 3.
333 can't be passed as keyword arguments to functions on Python 3.
333 """
334 """
334 dic = dict((k.decode('latin-1'), v) for k, v in dic.items())
335 dic = dict((k.decode('latin-1'), v) for k, v in dic.items())
335 return dic
336 return dic
336
337
337 def byteskwargs(dic):
338 def byteskwargs(dic):
338 """
339 """
339 Converts keys of python dictonaries to bytes as they were converted to
340 Converts keys of python dictonaries to bytes as they were converted to
340 str to pass that dictonary as a keyword argument on Python 3.
341 str to pass that dictonary as a keyword argument on Python 3.
341 """
342 """
342 dic = dict((k.encode('latin-1'), v) for k, v in dic.items())
343 dic = dict((k.encode('latin-1'), v) for k, v in dic.items())
343 return dic
344 return dic
344
345
345 # TODO: handle shlex.shlex().
346 # TODO: handle shlex.shlex().
346 def shlexsplit(s, comments=False, posix=True):
347 def shlexsplit(s, comments=False, posix=True):
347 """
348 """
348 Takes bytes argument, convert it to str i.e. unicodes, pass that into
349 Takes bytes argument, convert it to str i.e. unicodes, pass that into
349 shlex.split(), convert the returned value to bytes and return that for
350 shlex.split(), convert the returned value to bytes and return that for
350 Python 3 compatibility as shelx.split() don't accept bytes on Python 3.
351 Python 3 compatibility as shelx.split() don't accept bytes on Python 3.
351 """
352 """
352 ret = shlex.split(s.decode('latin-1'), comments, posix)
353 ret = shlex.split(s.decode('latin-1'), comments, posix)
353 return [a.encode('latin-1') for a in ret]
354 return [a.encode('latin-1') for a in ret]
354
355
355 iteritems = lambda x: x.items()
356 iteritems = lambda x: x.items()
356 itervalues = lambda x: x.values()
357 itervalues = lambda x: x.values()
357
358
358 # Python 3.5's json.load and json.loads require str. We polyfill its
359 # Python 3.5's json.load and json.loads require str. We polyfill its
359 # code for detecting encoding from bytes.
360 # code for detecting encoding from bytes.
360 if sys.version_info[0:2] < (3, 6):
361 if sys.version_info[0:2] < (3, 6):
361
362
362 def _detect_encoding(b):
363 def _detect_encoding(b):
363 bstartswith = b.startswith
364 bstartswith = b.startswith
364 if bstartswith((codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE)):
365 if bstartswith((codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE)):
365 return 'utf-32'
366 return 'utf-32'
366 if bstartswith((codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE)):
367 if bstartswith((codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE)):
367 return 'utf-16'
368 return 'utf-16'
368 if bstartswith(codecs.BOM_UTF8):
369 if bstartswith(codecs.BOM_UTF8):
369 return 'utf-8-sig'
370 return 'utf-8-sig'
370
371
371 if len(b) >= 4:
372 if len(b) >= 4:
372 if not b[0]:
373 if not b[0]:
373 # 00 00 -- -- - utf-32-be
374 # 00 00 -- -- - utf-32-be
374 # 00 XX -- -- - utf-16-be
375 # 00 XX -- -- - utf-16-be
375 return 'utf-16-be' if b[1] else 'utf-32-be'
376 return 'utf-16-be' if b[1] else 'utf-32-be'
376 if not b[1]:
377 if not b[1]:
377 # XX 00 00 00 - utf-32-le
378 # XX 00 00 00 - utf-32-le
378 # XX 00 00 XX - utf-16-le
379 # XX 00 00 XX - utf-16-le
379 # XX 00 XX -- - utf-16-le
380 # XX 00 XX -- - utf-16-le
380 return 'utf-16-le' if b[2] or b[3] else 'utf-32-le'
381 return 'utf-16-le' if b[2] or b[3] else 'utf-32-le'
381 elif len(b) == 2:
382 elif len(b) == 2:
382 if not b[0]:
383 if not b[0]:
383 # 00 XX - utf-16-be
384 # 00 XX - utf-16-be
384 return 'utf-16-be'
385 return 'utf-16-be'
385 if not b[1]:
386 if not b[1]:
386 # XX 00 - utf-16-le
387 # XX 00 - utf-16-le
387 return 'utf-16-le'
388 return 'utf-16-le'
388 # default
389 # default
389 return 'utf-8'
390 return 'utf-8'
390
391
391 def json_loads(s, *args, **kwargs):
392 def json_loads(s, *args, **kwargs):
392 if isinstance(s, (bytes, bytearray)):
393 if isinstance(s, (bytes, bytearray)):
393 s = s.decode(_detect_encoding(s), 'surrogatepass')
394 s = s.decode(_detect_encoding(s), 'surrogatepass')
394
395
395 return json.loads(s, *args, **kwargs)
396 return json.loads(s, *args, **kwargs)
396
397
397 else:
398 else:
398 json_loads = json.loads
399 json_loads = json.loads
399
400
400 else:
401 else:
401 import cStringIO
402 import cStringIO
402
403
403 xrange = xrange
404 xrange = xrange
404 unicode = unicode
405 unicode = unicode
405 bytechr = chr
406 bytechr = chr
406 byterepr = repr
407 byterepr = repr
407 bytestr = str
408 bytestr = str
408 iterbytestr = iter
409 iterbytestr = iter
409 maybebytestr = identity
410 maybebytestr = identity
410 sysbytes = identity
411 sysbytes = identity
411 sysstr = identity
412 sysstr = identity
412 strurl = identity
413 strurl = identity
413 bytesurl = identity
414 bytesurl = identity
414 open = open
415 open = open
415 delattr = delattr
416 delattr = delattr
416 getattr = getattr
417 getattr = getattr
417 hasattr = hasattr
418 hasattr = hasattr
418 setattr = setattr
419 setattr = setattr
419
420
420 # this can't be parsed on Python 3
421 # this can't be parsed on Python 3
421 exec(b'def raisewithtb(exc, tb):\n raise exc, None, tb\n')
422 exec(b'def raisewithtb(exc, tb):\n raise exc, None, tb\n')
422
423
423 def fsencode(filename):
424 def fsencode(filename):
424 """
425 """
425 Partial backport from os.py in Python 3, which only accepts bytes.
426 Partial backport from os.py in Python 3, which only accepts bytes.
426 In Python 2, our paths should only ever be bytes, a unicode path
427 In Python 2, our paths should only ever be bytes, a unicode path
427 indicates a bug.
428 indicates a bug.
428 """
429 """
429 if isinstance(filename, str):
430 if isinstance(filename, str):
430 return filename
431 return filename
431 else:
432 else:
432 raise TypeError("expect str, not %s" % type(filename).__name__)
433 raise TypeError("expect str, not %s" % type(filename).__name__)
433
434
434 # In Python 2, fsdecode() has a very chance to receive bytes. So it's
435 # In Python 2, fsdecode() has a very chance to receive bytes. So it's
435 # better not to touch Python 2 part as it's already working fine.
436 # better not to touch Python 2 part as it's already working fine.
436 fsdecode = identity
437 fsdecode = identity
437
438
438 def getdoc(obj):
439 def getdoc(obj):
439 return getattr(obj, '__doc__', None)
440 return getattr(obj, '__doc__', None)
440
441
441 _notset = object()
442 _notset = object()
442
443
443 def safehasattr(thing, attr):
444 def safehasattr(thing, attr):
444 return getattr(thing, attr, _notset) is not _notset
445 return getattr(thing, attr, _notset) is not _notset
445
446
446 def _getoptbwrapper(orig, args, shortlist, namelist):
447 def _getoptbwrapper(orig, args, shortlist, namelist):
447 return orig(args, shortlist, namelist)
448 return orig(args, shortlist, namelist)
448
449
449 strkwargs = identity
450 strkwargs = identity
450 byteskwargs = identity
451 byteskwargs = identity
451
452
452 oscurdir = os.curdir
453 oscurdir = os.curdir
453 oslinesep = os.linesep
454 oslinesep = os.linesep
454 osname = os.name
455 osname = os.name
455 ospathsep = os.pathsep
456 ospathsep = os.pathsep
456 ospardir = os.pardir
457 ospardir = os.pardir
457 ossep = os.sep
458 ossep = os.sep
458 osaltsep = os.altsep
459 osaltsep = os.altsep
460 osdevnull = os.devnull
459 long = long
461 long = long
460 stdin = sys.stdin
462 stdin = sys.stdin
461 stdout = sys.stdout
463 stdout = sys.stdout
462 stderr = sys.stderr
464 stderr = sys.stderr
463 if getattr(sys, 'argv', None) is not None:
465 if getattr(sys, 'argv', None) is not None:
464 sysargv = sys.argv
466 sysargv = sys.argv
465 sysplatform = sys.platform
467 sysplatform = sys.platform
466 sysexecutable = sys.executable
468 sysexecutable = sys.executable
467 shlexsplit = shlex.split
469 shlexsplit = shlex.split
468 bytesio = cStringIO.StringIO
470 bytesio = cStringIO.StringIO
469 stringio = bytesio
471 stringio = bytesio
470 maplist = map
472 maplist = map
471 rangelist = range
473 rangelist = range
472 ziplist = zip
474 ziplist = zip
473 rawinput = raw_input
475 rawinput = raw_input
474 getargspec = inspect.getargspec
476 getargspec = inspect.getargspec
475 iteritems = lambda x: x.iteritems()
477 iteritems = lambda x: x.iteritems()
476 itervalues = lambda x: x.itervalues()
478 itervalues = lambda x: x.itervalues()
477 json_loads = json.loads
479 json_loads = json.loads
478
480
479 isjython = sysplatform.startswith(b'java')
481 isjython = sysplatform.startswith(b'java')
480
482
481 isdarwin = sysplatform.startswith(b'darwin')
483 isdarwin = sysplatform.startswith(b'darwin')
482 islinux = sysplatform.startswith(b'linux')
484 islinux = sysplatform.startswith(b'linux')
483 isposix = osname == b'posix'
485 isposix = osname == b'posix'
484 iswindows = osname == b'nt'
486 iswindows = osname == b'nt'
485
487
486
488
487 def getoptb(args, shortlist, namelist):
489 def getoptb(args, shortlist, namelist):
488 return _getoptbwrapper(getopt.getopt, args, shortlist, namelist)
490 return _getoptbwrapper(getopt.getopt, args, shortlist, namelist)
489
491
490
492
491 def gnugetoptb(args, shortlist, namelist):
493 def gnugetoptb(args, shortlist, namelist):
492 return _getoptbwrapper(getopt.gnu_getopt, args, shortlist, namelist)
494 return _getoptbwrapper(getopt.gnu_getopt, args, shortlist, namelist)
493
495
494
496
495 def mkdtemp(suffix=b'', prefix=b'tmp', dir=None):
497 def mkdtemp(suffix=b'', prefix=b'tmp', dir=None):
496 return tempfile.mkdtemp(suffix, prefix, dir)
498 return tempfile.mkdtemp(suffix, prefix, dir)
497
499
498
500
499 # text=True is not supported; use util.from/tonativeeol() instead
501 # text=True is not supported; use util.from/tonativeeol() instead
500 def mkstemp(suffix=b'', prefix=b'tmp', dir=None):
502 def mkstemp(suffix=b'', prefix=b'tmp', dir=None):
501 return tempfile.mkstemp(suffix, prefix, dir)
503 return tempfile.mkstemp(suffix, prefix, dir)
502
504
503
505
504 # mode must include 'b'ytes as encoding= is not supported
506 # mode must include 'b'ytes as encoding= is not supported
505 def namedtempfile(
507 def namedtempfile(
506 mode=b'w+b', bufsize=-1, suffix=b'', prefix=b'tmp', dir=None, delete=True
508 mode=b'w+b', bufsize=-1, suffix=b'', prefix=b'tmp', dir=None, delete=True
507 ):
509 ):
508 mode = sysstr(mode)
510 mode = sysstr(mode)
509 assert 'b' in mode
511 assert 'b' in mode
510 return tempfile.NamedTemporaryFile(
512 return tempfile.NamedTemporaryFile(
511 mode, bufsize, suffix=suffix, prefix=prefix, dir=dir, delete=delete
513 mode, bufsize, suffix=suffix, prefix=prefix, dir=dir, delete=delete
512 )
514 )
@@ -1,517 +1,541 b''
1 $ echo "[extensions]" >> $HGRCPATH
1 $ echo "[extensions]" >> $HGRCPATH
2 $ echo "extdiff=" >> $HGRCPATH
2 $ echo "extdiff=" >> $HGRCPATH
3
3
4 $ hg init a
4 $ hg init a
5 $ cd a
5 $ cd a
6 $ echo a > a
6 $ echo a > a
7 $ echo b > b
7 $ echo b > b
8 $ hg add
8 $ hg add
9 adding a
9 adding a
10 adding b
10 adding b
11
11
12 Should diff cloned directories:
12 Should diff cloned directories:
13
13
14 $ hg extdiff -o -r $opt
14 $ hg extdiff -o -r $opt
15 Only in a: a
15 Only in a: a
16 Only in a: b
16 Only in a: b
17 [1]
17 [1]
18
18
19 $ cat <<EOF >> $HGRCPATH
19 $ cat <<EOF >> $HGRCPATH
20 > [extdiff]
20 > [extdiff]
21 > cmd.falabala = echo
21 > cmd.falabala = echo
22 > opts.falabala = diffing
22 > opts.falabala = diffing
23 > cmd.edspace = echo
23 > cmd.edspace = echo
24 > opts.edspace = "name <user@example.com>"
24 > opts.edspace = "name <user@example.com>"
25 > alabalaf =
25 > alabalaf =
26 > [merge-tools]
26 > [merge-tools]
27 > alabalaf.executable = echo
27 > alabalaf.executable = echo
28 > alabalaf.diffargs = diffing
28 > alabalaf.diffargs = diffing
29 > EOF
29 > EOF
30
30
31 $ hg falabala
31 $ hg falabala
32 diffing a.000000000000 a
32 diffing a.000000000000 a
33 [1]
33 [1]
34
34
35 $ hg help falabala
35 $ hg help falabala
36 hg falabala [OPTION]... [FILE]...
36 hg falabala [OPTION]... [FILE]...
37
37
38 use external program to diff repository (or selected files)
38 use external program to diff repository (or selected files)
39
39
40 Show differences between revisions for the specified files, using the
40 Show differences between revisions for the specified files, using the
41 following program:
41 following program:
42
42
43 'echo'
43 'echo'
44
44
45 When two revision arguments are given, then changes are shown between
45 When two revision arguments are given, then changes are shown between
46 those revisions. If only one revision is specified then that revision is
46 those revisions. If only one revision is specified then that revision is
47 compared to the working directory, and, when no revisions are specified,
47 compared to the working directory, and, when no revisions are specified,
48 the working directory files are compared to its parent.
48 the working directory files are compared to its parent.
49
49
50 options ([+] can be repeated):
50 options ([+] can be repeated):
51
51
52 -o --option OPT [+] pass option to comparison program
52 -o --option OPT [+] pass option to comparison program
53 -r --rev REV [+] revision
53 -r --rev REV [+] revision
54 -c --change REV change made by revision
54 -c --change REV change made by revision
55 --per-file compare each file instead of revision snapshots
55 --per-file compare each file instead of revision snapshots
56 --confirm prompt user before each external program invocation
56 --confirm prompt user before each external program invocation
57 --patch compare patches for two revisions
57 --patch compare patches for two revisions
58 -I --include PATTERN [+] include names matching the given patterns
58 -I --include PATTERN [+] include names matching the given patterns
59 -X --exclude PATTERN [+] exclude names matching the given patterns
59 -X --exclude PATTERN [+] exclude names matching the given patterns
60 -S --subrepos recurse into subrepositories
60 -S --subrepos recurse into subrepositories
61
61
62 (some details hidden, use --verbose to show complete help)
62 (some details hidden, use --verbose to show complete help)
63
63
64 $ hg ci -d '0 0' -mtest1
64 $ hg ci -d '0 0' -mtest1
65
65
66 $ echo b >> a
66 $ echo b >> a
67 $ hg ci -d '1 0' -mtest2
67 $ hg ci -d '1 0' -mtest2
68
68
69 Should diff cloned files directly:
69 Should diff cloned files directly:
70
70
71 $ hg falabala -r 0:1
71 $ hg falabala -r 0:1
72 diffing "*\\extdiff.*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
72 diffing "*\\extdiff.*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
73 diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
73 diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
74 [1]
74 [1]
75
75
76 Specifying an empty revision should abort.
76 Specifying an empty revision should abort.
77
77
78 $ hg extdiff -p diff --patch --rev 'ancestor()' --rev 1
78 $ hg extdiff -p diff --patch --rev 'ancestor()' --rev 1
79 abort: empty revision on one side of range
79 abort: empty revision on one side of range
80 [255]
80 [255]
81
81
82 Test diff during merge:
82 Test diff during merge:
83
83
84 $ hg update -C 0
84 $ hg update -C 0
85 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
85 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
86 $ echo c >> c
86 $ echo c >> c
87 $ hg add c
87 $ hg add c
88 $ hg ci -m "new branch" -d '1 0'
88 $ hg ci -m "new branch" -d '1 0'
89 created new head
89 created new head
90 $ hg merge 1
90 $ hg merge 1
91 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
91 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
92 (branch merge, don't forget to commit)
92 (branch merge, don't forget to commit)
93
93
94 Should diff cloned file against wc file:
94 Should diff cloned file against wc file:
95
95
96 $ hg falabala
96 $ hg falabala
97 diffing "*\\extdiff.*\\a.2a13a4d2da36\\a" "*\\a\\a" (glob) (windows !)
97 diffing "*\\extdiff.*\\a.2a13a4d2da36\\a" "*\\a\\a" (glob) (windows !)
98 diffing */extdiff.*/a.2a13a4d2da36/a */a/a (glob) (no-windows !)
98 diffing */extdiff.*/a.2a13a4d2da36/a */a/a (glob) (no-windows !)
99 [1]
99 [1]
100
100
101
101
102 Test --change option:
102 Test --change option:
103
103
104 $ hg ci -d '2 0' -mtest3
104 $ hg ci -d '2 0' -mtest3
105
105
106 $ hg falabala -c 1
106 $ hg falabala -c 1
107 diffing "*\\extdiff.*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
107 diffing "*\\extdiff.*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
108 diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
108 diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
109 [1]
109 [1]
110
110
111 Check diff are made from the first parent:
111 Check diff are made from the first parent:
112
112
113 $ hg falabala -c 3 || echo "diff-like tools yield a non-zero exit code"
113 $ hg falabala -c 3 || echo "diff-like tools yield a non-zero exit code"
114 diffing "*\\extdiff.*\\a.2a13a4d2da36\\a" "a.46c0e4daeb72\\a" (glob) (windows !)
114 diffing "*\\extdiff.*\\a.2a13a4d2da36\\a" "a.46c0e4daeb72\\a" (glob) (windows !)
115 diffing */extdiff.*/a.2a13a4d2da36/a a.46c0e4daeb72/a (glob) (no-windows !)
115 diffing */extdiff.*/a.2a13a4d2da36/a a.46c0e4daeb72/a (glob) (no-windows !)
116 diff-like tools yield a non-zero exit code
116 diff-like tools yield a non-zero exit code
117
117
118 issue3153: ensure using extdiff with removed subrepos doesn't crash:
118 issue3153: ensure using extdiff with removed subrepos doesn't crash:
119
119
120 $ hg init suba
120 $ hg init suba
121 $ cd suba
121 $ cd suba
122 $ echo suba > suba
122 $ echo suba > suba
123 $ hg add
123 $ hg add
124 adding suba
124 adding suba
125 $ hg ci -m "adding suba file"
125 $ hg ci -m "adding suba file"
126 $ cd ..
126 $ cd ..
127 $ echo suba=suba > .hgsub
127 $ echo suba=suba > .hgsub
128 $ hg add
128 $ hg add
129 adding .hgsub
129 adding .hgsub
130 $ hg ci -Sm "adding subrepo"
130 $ hg ci -Sm "adding subrepo"
131 $ echo > .hgsub
131 $ echo > .hgsub
132 $ hg ci -m "removing subrepo"
132 $ hg ci -m "removing subrepo"
133 $ hg falabala -r 4 -r 5 -S
133 $ hg falabala -r 4 -r 5 -S
134 diffing a.398e36faf9c6 a.5ab95fb166c4
134 diffing a.398e36faf9c6 a.5ab95fb166c4
135 [1]
135 [1]
136
136
137 Test --per-file option:
137 Test --per-file option:
138
138
139 $ hg up -q -C 3
139 $ hg up -q -C 3
140 $ echo a2 > a
140 $ echo a2 > a
141 $ echo b2 > b
141 $ echo b2 > b
142 $ hg ci -d '3 0' -mtestmode1
142 $ hg ci -d '3 0' -mtestmode1
143 created new head
143 created new head
144 $ hg falabala -c 6 --per-file
144 $ hg falabala -c 6 --per-file
145 diffing "*\\extdiff.*\\a.46c0e4daeb72\\a" "a.81906f2b98ac\\a" (glob) (windows !)
145 diffing "*\\extdiff.*\\a.46c0e4daeb72\\a" "a.81906f2b98ac\\a" (glob) (windows !)
146 diffing */extdiff.*/a.46c0e4daeb72/a a.81906f2b98ac/a (glob) (no-windows !)
146 diffing */extdiff.*/a.46c0e4daeb72/a a.81906f2b98ac/a (glob) (no-windows !)
147 diffing "*\\extdiff.*\\a.46c0e4daeb72\\b" "a.81906f2b98ac\\b" (glob) (windows !)
147 diffing "*\\extdiff.*\\a.46c0e4daeb72\\b" "a.81906f2b98ac\\b" (glob) (windows !)
148 diffing */extdiff.*/a.46c0e4daeb72/b a.81906f2b98ac/b (glob) (no-windows !)
148 diffing */extdiff.*/a.46c0e4daeb72/b a.81906f2b98ac/b (glob) (no-windows !)
149 [1]
149 [1]
150
150
151 Test --per-file option for gui tool:
151 Test --per-file option for gui tool:
152
152
153 $ hg --config extdiff.gui.alabalaf=True alabalaf -c 6 --per-file --debug
153 $ hg --config extdiff.gui.alabalaf=True alabalaf -c 6 --per-file --debug
154 diffing */extdiff.*/a.46c0e4daeb72/* a.81906f2b98ac/* (glob)
154 diffing */extdiff.*/a.46c0e4daeb72/* a.81906f2b98ac/* (glob)
155 diffing */extdiff.*/a.46c0e4daeb72/* a.81906f2b98ac/* (glob)
155 diffing */extdiff.*/a.46c0e4daeb72/* a.81906f2b98ac/* (glob)
156 making snapshot of 2 files from rev 46c0e4daeb72
156 making snapshot of 2 files from rev 46c0e4daeb72
157 a
157 a
158 b
158 b
159 making snapshot of 2 files from rev 81906f2b98ac
159 making snapshot of 2 files from rev 81906f2b98ac
160 a
160 a
161 b
161 b
162 running '* diffing * *' in * (backgrounded) (glob)
162 running '* diffing * *' in * (backgrounded) (glob)
163 running '* diffing * *' in * (backgrounded) (glob)
163 running '* diffing * *' in * (backgrounded) (glob)
164 cleaning up temp directory
164 cleaning up temp directory
165 [1]
165 [1]
166
166
167 Test --per-file option for gui tool again:
167 Test --per-file option for gui tool again:
168
168
169 $ hg --config merge-tools.alabalaf.gui=True alabalaf -c 6 --per-file --debug
169 $ hg --config merge-tools.alabalaf.gui=True alabalaf -c 6 --per-file --debug
170 diffing */extdiff.*/a.46c0e4daeb72/* a.81906f2b98ac/* (glob)
170 diffing */extdiff.*/a.46c0e4daeb72/* a.81906f2b98ac/* (glob)
171 diffing */extdiff.*/a.46c0e4daeb72/* a.81906f2b98ac/* (glob)
171 diffing */extdiff.*/a.46c0e4daeb72/* a.81906f2b98ac/* (glob)
172 making snapshot of 2 files from rev 46c0e4daeb72
172 making snapshot of 2 files from rev 46c0e4daeb72
173 a
173 a
174 b
174 b
175 making snapshot of 2 files from rev 81906f2b98ac
175 making snapshot of 2 files from rev 81906f2b98ac
176 a
176 a
177 b
177 b
178 running '* diffing * *' in * (backgrounded) (glob)
178 running '* diffing * *' in * (backgrounded) (glob)
179 running '* diffing * *' in * (backgrounded) (glob)
179 running '* diffing * *' in * (backgrounded) (glob)
180 cleaning up temp directory
180 cleaning up temp directory
181 [1]
181 [1]
182
182
183 Test --per-file and --confirm options:
183 Test --per-file and --confirm options:
184
184
185 $ hg --config ui.interactive=True falabala -c 6 --per-file --confirm <<EOF
185 $ hg --config ui.interactive=True falabala -c 6 --per-file --confirm <<EOF
186 > n
186 > n
187 > y
187 > y
188 > EOF
188 > EOF
189 diff a (1 of 2) [Yns?] n
189 diff a (1 of 2) [Yns?] n
190 diff b (2 of 2) [Yns?] y
190 diff b (2 of 2) [Yns?] y
191 diffing "*\\extdiff.*\\a.46c0e4daeb72\\b" "a.81906f2b98ac\\b" (glob) (windows !)
191 diffing "*\\extdiff.*\\a.46c0e4daeb72\\b" "a.81906f2b98ac\\b" (glob) (windows !)
192 diffing */extdiff.*/a.46c0e4daeb72/b a.81906f2b98ac/b (glob) (no-windows !)
192 diffing */extdiff.*/a.46c0e4daeb72/b a.81906f2b98ac/b (glob) (no-windows !)
193 [1]
193 [1]
194
194
195 Test --per-file and --confirm options with skipping:
195 Test --per-file and --confirm options with skipping:
196
196
197 $ hg --config ui.interactive=True falabala -c 6 --per-file --confirm <<EOF
197 $ hg --config ui.interactive=True falabala -c 6 --per-file --confirm <<EOF
198 > s
198 > s
199 > EOF
199 > EOF
200 diff a (1 of 2) [Yns?] s
200 diff a (1 of 2) [Yns?] s
201 [1]
201 [1]
202
202
203 issue4463: usage of command line configuration without additional quoting
203 issue4463: usage of command line configuration without additional quoting
204
204
205 $ cat <<EOF >> $HGRCPATH
205 $ cat <<EOF >> $HGRCPATH
206 > [extdiff]
206 > [extdiff]
207 > cmd.4463a = echo
207 > cmd.4463a = echo
208 > opts.4463a = a-naked 'single quoted' "double quoted"
208 > opts.4463a = a-naked 'single quoted' "double quoted"
209 > 4463b = echo b-naked 'single quoted' "double quoted"
209 > 4463b = echo b-naked 'single quoted' "double quoted"
210 > echo =
210 > echo =
211 > EOF
211 > EOF
212 $ hg update -q -C 0
212 $ hg update -q -C 0
213 $ echo a >> a
213 $ echo a >> a
214
214
215 $ hg --debug 4463a | grep '^running'
215 $ hg --debug 4463a | grep '^running'
216 running 'echo a-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
216 running 'echo a-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
217 running 'echo a-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
217 running 'echo a-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
218 $ hg --debug 4463b | grep '^running'
218 $ hg --debug 4463b | grep '^running'
219 running 'echo b-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
219 running 'echo b-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
220 running 'echo b-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
220 running 'echo b-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
221 $ hg --debug echo | grep '^running'
221 $ hg --debug echo | grep '^running'
222 running '*echo* "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
222 running '*echo* "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
223 running '*echo */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
223 running '*echo */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
224
224
225 (getting options from other than extdiff section)
225 (getting options from other than extdiff section)
226
226
227 $ cat <<EOF >> $HGRCPATH
227 $ cat <<EOF >> $HGRCPATH
228 > [extdiff]
228 > [extdiff]
229 > # using diff-tools diffargs
229 > # using diff-tools diffargs
230 > 4463b2 = echo
230 > 4463b2 = echo
231 > # using merge-tools diffargs
231 > # using merge-tools diffargs
232 > 4463b3 = echo
232 > 4463b3 = echo
233 > # no diffargs
233 > # no diffargs
234 > 4463b4 = echo
234 > 4463b4 = echo
235 > [diff-tools]
235 > [diff-tools]
236 > 4463b2.diffargs = b2-naked 'single quoted' "double quoted"
236 > 4463b2.diffargs = b2-naked 'single quoted' "double quoted"
237 > [merge-tools]
237 > [merge-tools]
238 > 4463b3.diffargs = b3-naked 'single quoted' "double quoted"
238 > 4463b3.diffargs = b3-naked 'single quoted' "double quoted"
239 > EOF
239 > EOF
240
240
241 $ hg --debug 4463b2 | grep '^running'
241 $ hg --debug 4463b2 | grep '^running'
242 running 'echo b2-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
242 running 'echo b2-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
243 running 'echo b2-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
243 running 'echo b2-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
244 $ hg --debug 4463b3 | grep '^running'
244 $ hg --debug 4463b3 | grep '^running'
245 running 'echo b3-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
245 running 'echo b3-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
246 running 'echo b3-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
246 running 'echo b3-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
247 $ hg --debug 4463b4 | grep '^running'
247 $ hg --debug 4463b4 | grep '^running'
248 running 'echo "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
248 running 'echo "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
249 running 'echo */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
249 running 'echo */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
250 $ hg --debug 4463b4 --option b4-naked --option 'being quoted' | grep '^running'
250 $ hg --debug 4463b4 --option b4-naked --option 'being quoted' | grep '^running'
251 running 'echo b4-naked "being quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
251 running 'echo b4-naked "being quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
252 running "echo b4-naked 'being quoted' */a $TESTTMP/a/a" in */extdiff.* (glob) (no-windows !)
252 running "echo b4-naked 'being quoted' */a $TESTTMP/a/a" in */extdiff.* (glob) (no-windows !)
253 $ hg --debug extdiff -p echo --option echo-naked --option 'being quoted' | grep '^running'
253 $ hg --debug extdiff -p echo --option echo-naked --option 'being quoted' | grep '^running'
254 running 'echo echo-naked "being quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
254 running 'echo echo-naked "being quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
255 running "echo echo-naked 'being quoted' */a $TESTTMP/a/a" in */extdiff.* (glob) (no-windows !)
255 running "echo echo-naked 'being quoted' */a $TESTTMP/a/a" in */extdiff.* (glob) (no-windows !)
256
256
257 $ touch 'sp ace'
257 $ touch 'sp ace'
258 $ hg add 'sp ace'
258 $ hg add 'sp ace'
259 $ hg ci -m 'sp ace'
259 $ hg ci -m 'sp ace'
260 created new head
260 created new head
261 $ echo > 'sp ace'
261 $ echo > 'sp ace'
262
262
263 Test pre-72a89cf86fcd backward compatibility with half-baked manual quoting
263 Test pre-72a89cf86fcd backward compatibility with half-baked manual quoting
264
264
265 $ cat <<EOF >> $HGRCPATH
265 $ cat <<EOF >> $HGRCPATH
266 > [extdiff]
266 > [extdiff]
267 > odd =
267 > odd =
268 > [merge-tools]
268 > [merge-tools]
269 > odd.diffargs = --foo='\$clabel' '\$clabel' "--bar=\$clabel" "\$clabel"
269 > odd.diffargs = --foo='\$clabel' '\$clabel' "--bar=\$clabel" "\$clabel"
270 > odd.executable = echo
270 > odd.executable = echo
271 > EOF
271 > EOF
272
272
273 $ hg --debug odd | grep '^running'
273 $ hg --debug odd | grep '^running'
274 running '"*\\echo.exe" --foo="sp ace" "sp ace" --bar="sp ace" "sp ace"' in * (glob) (windows !)
274 running '"*\\echo.exe" --foo="sp ace" "sp ace" --bar="sp ace" "sp ace"' in * (glob) (windows !)
275 running "*/echo --foo='sp ace' 'sp ace' --bar='sp ace' 'sp ace'" in * (glob) (no-windows !)
275 running "*/echo --foo='sp ace' 'sp ace' --bar='sp ace' 'sp ace'" in * (glob) (no-windows !)
276
276
277 Empty argument must be quoted
277 Empty argument must be quoted
278
278
279 $ cat <<EOF >> $HGRCPATH
279 $ cat <<EOF >> $HGRCPATH
280 > [extdiff]
280 > [extdiff]
281 > kdiff3 = echo
281 > kdiff3 = echo
282 > [merge-tools]
282 > [merge-tools]
283 > kdiff3.diffargs=--L1 \$plabel1 --L2 \$clabel \$parent \$child
283 > kdiff3.diffargs=--L1 \$plabel1 --L2 \$clabel \$parent \$child
284 > EOF
284 > EOF
285
285
286 $ hg --debug kdiff3 -r0 | grep '^running'
286 $ hg --debug kdiff3 -r0 | grep '^running'
287 running 'echo --L1 "@0" --L2 "" a.8a5febb7f867 a' in * (glob) (windows !)
287 running 'echo --L1 "@0" --L2 "" a.8a5febb7f867 a' in * (glob) (windows !)
288 running "echo --L1 '@0' --L2 '' a.8a5febb7f867 a" in * (glob) (no-windows !)
288 running "echo --L1 '@0' --L2 '' a.8a5febb7f867 a" in * (glob) (no-windows !)
289
289
290
290
291 Test extdiff of multiple files in tmp dir:
291 Test extdiff of multiple files in tmp dir:
292
292
293 $ hg update -C 0 > /dev/null
293 $ hg update -C 0 > /dev/null
294 $ echo changed > a
294 $ echo changed > a
295 $ echo changed > b
295 $ echo changed > b
296 #if execbit
296 #if execbit
297 $ chmod +x b
297 $ chmod +x b
298 #endif
298 #endif
299
299
300 Diff in working directory, before:
300 Diff in working directory, before:
301
301
302 $ hg diff --git
302 $ hg diff --git
303 diff --git a/a b/a
303 diff --git a/a b/a
304 --- a/a
304 --- a/a
305 +++ b/a
305 +++ b/a
306 @@ -1,1 +1,1 @@
306 @@ -1,1 +1,1 @@
307 -a
307 -a
308 +changed
308 +changed
309 diff --git a/b b/b
309 diff --git a/b b/b
310 old mode 100644 (execbit !)
310 old mode 100644 (execbit !)
311 new mode 100755 (execbit !)
311 new mode 100755 (execbit !)
312 --- a/b
312 --- a/b
313 +++ b/b
313 +++ b/b
314 @@ -1,1 +1,1 @@
314 @@ -1,1 +1,1 @@
315 -b
315 -b
316 +changed
316 +changed
317
317
318
318
319 Edit with extdiff -p:
319 Edit with extdiff -p:
320
320
321 Prepare custom diff/edit tool:
321 Prepare custom diff/edit tool:
322
322
323 $ cat > 'diff tool.py' << EOT
323 $ cat > 'diff tool.py' << EOT
324 > #!$PYTHON
324 > #!$PYTHON
325 > import time
325 > import time
326 > time.sleep(1) # avoid unchanged-timestamp problems
326 > time.sleep(1) # avoid unchanged-timestamp problems
327 > open('a/a', 'ab').write(b'edited\n')
327 > open('a/a', 'ab').write(b'edited\n')
328 > open('a/b', 'ab').write(b'edited\n')
328 > open('a/b', 'ab').write(b'edited\n')
329 > EOT
329 > EOT
330
330
331 #if execbit
331 #if execbit
332 $ chmod +x 'diff tool.py'
332 $ chmod +x 'diff tool.py'
333 #endif
333 #endif
334
334
335 will change to /tmp/extdiff.TMP and populate directories a.TMP and a
335 will change to /tmp/extdiff.TMP and populate directories a.TMP and a
336 and start tool
336 and start tool
337
337
338 #if windows
338 #if windows
339 $ cat > 'diff tool.bat' << EOF
339 $ cat > 'diff tool.bat' << EOF
340 > @"$PYTHON" "`pwd`/diff tool.py"
340 > @"$PYTHON" "`pwd`/diff tool.py"
341 > EOF
341 > EOF
342 $ hg extdiff -p "`pwd`/diff tool.bat"
342 $ hg extdiff -p "`pwd`/diff tool.bat"
343 [1]
343 [1]
344 #else
344 #else
345 $ hg extdiff -p "`pwd`/diff tool.py"
345 $ hg extdiff -p "`pwd`/diff tool.py"
346 [1]
346 [1]
347 #endif
347 #endif
348
348
349 Diff in working directory, after:
349 Diff in working directory, after:
350
350
351 $ hg diff --git
351 $ hg diff --git
352 diff --git a/a b/a
352 diff --git a/a b/a
353 --- a/a
353 --- a/a
354 +++ b/a
354 +++ b/a
355 @@ -1,1 +1,2 @@
355 @@ -1,1 +1,2 @@
356 -a
356 -a
357 +changed
357 +changed
358 +edited
358 +edited
359 diff --git a/b b/b
359 diff --git a/b b/b
360 old mode 100644 (execbit !)
360 old mode 100644 (execbit !)
361 new mode 100755 (execbit !)
361 new mode 100755 (execbit !)
362 --- a/b
362 --- a/b
363 +++ b/b
363 +++ b/b
364 @@ -1,1 +1,2 @@
364 @@ -1,1 +1,2 @@
365 -b
365 -b
366 +changed
366 +changed
367 +edited
367 +edited
368
368
369 Test extdiff with --option:
369 Test extdiff with --option:
370
370
371 $ hg extdiff -p echo -o this -c 1
371 $ hg extdiff -p echo -o this -c 1
372 this "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
372 this "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
373 this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
373 this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
374 [1]
374 [1]
375
375
376 $ hg falabala -o this -c 1
376 $ hg falabala -o this -c 1
377 diffing this "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
377 diffing this "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
378 diffing this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
378 diffing this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
379 [1]
379 [1]
380
380
381 Test extdiff's handling of options with spaces in them:
381 Test extdiff's handling of options with spaces in them:
382
382
383 $ hg edspace -c 1
383 $ hg edspace -c 1
384 "name <user@example.com>" "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
384 "name <user@example.com>" "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
385 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
385 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
386 [1]
386 [1]
387
387
388 $ hg extdiff -p echo -o "name <user@example.com>" -c 1
388 $ hg extdiff -p echo -o "name <user@example.com>" -c 1
389 "name <user@example.com>" "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
389 "name <user@example.com>" "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
390 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
390 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
391 [1]
391 [1]
392
392
393 Test with revsets:
393 Test with revsets:
394
394
395 $ hg extdif -p echo -c "rev(1)"
395 $ hg extdif -p echo -c "rev(1)"
396 "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
396 "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
397 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
397 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
398 [1]
398 [1]
399
399
400 $ hg extdif -p echo -r "0::1"
400 $ hg extdif -p echo -r "0::1"
401 "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
401 "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
402 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
402 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
403 [1]
403 [1]
404
404
405 Fallback to merge-tools.tool.executable|regkey
405 Fallback to merge-tools.tool.executable|regkey
406 $ mkdir dir
406 $ mkdir dir
407 $ cat > 'dir/tool.sh' << 'EOF'
407 $ cat > 'dir/tool.sh' << 'EOF'
408 > #!/bin/sh
408 > #!/bin/sh
409 > # Mimic a tool that syncs all attrs, including mtime
409 > # Mimic a tool that syncs all attrs, including mtime
410 > cp $1/a $2/a
410 > cp $1/a $2/a
411 > touch -r $1/a $2/a
411 > touch -r $1/a $2/a
412 > chmod +x $2/a
412 > chmod +x $2/a
413 > echo "** custom diff **"
413 > echo "** custom diff **"
414 > EOF
414 > EOF
415 #if execbit
415 #if execbit
416 $ chmod +x dir/tool.sh
416 $ chmod +x dir/tool.sh
417 #endif
417 #endif
418
418
419 Windows can't run *.sh directly, so create a shim executable that can be.
419 Windows can't run *.sh directly, so create a shim executable that can be.
420 Without something executable, the next hg command will try to run `tl` instead
420 Without something executable, the next hg command will try to run `tl` instead
421 of $tool (and fail).
421 of $tool (and fail).
422 #if windows
422 #if windows
423 $ cat > dir/tool.bat <<EOF
423 $ cat > dir/tool.bat <<EOF
424 > @sh -c "`pwd`/dir/tool.sh %1 %2"
424 > @sh -c "`pwd`/dir/tool.sh %1 %2"
425 > EOF
425 > EOF
426 $ tool=`pwd`/dir/tool.bat
426 $ tool=`pwd`/dir/tool.bat
427 #else
427 #else
428 $ tool=`pwd`/dir/tool.sh
428 $ tool=`pwd`/dir/tool.sh
429 #endif
429 #endif
430
430
431 $ cat a
431 $ cat a
432 changed
432 changed
433 edited
433 edited
434 $ hg --debug tl --config extdiff.tl= --config merge-tools.tl.executable=$tool
434 $ hg --debug tl --config extdiff.tl= --config merge-tools.tl.executable=$tool
435 making snapshot of 2 files from rev * (glob)
435 making snapshot of 2 files from rev * (glob)
436 a
436 a
437 b
437 b
438 making snapshot of 2 files from working directory
438 making snapshot of 2 files from working directory
439 a
439 a
440 b
440 b
441 running '$TESTTMP/a/dir/tool.bat a.* a' in */extdiff.* (glob) (windows !)
441 running '$TESTTMP/a/dir/tool.bat a.* a' in */extdiff.* (glob) (windows !)
442 running '$TESTTMP/a/dir/tool.sh a.* a' in */extdiff.* (glob) (no-windows !)
442 running '$TESTTMP/a/dir/tool.sh a.* a' in */extdiff.* (glob) (no-windows !)
443 ** custom diff **
443 ** custom diff **
444 file changed while diffing. Overwriting: $TESTTMP/a/a (src: */extdiff.*/a/a) (glob)
444 file changed while diffing. Overwriting: $TESTTMP/a/a (src: */extdiff.*/a/a) (glob)
445 cleaning up temp directory
445 cleaning up temp directory
446 [1]
446 [1]
447 $ cat a
447 $ cat a
448 a
448 a
449
449
450 #if execbit
450 #if execbit
451 $ [ -x a ]
451 $ [ -x a ]
452
452
453 $ cat > 'dir/tool.sh' << 'EOF'
453 $ cat > 'dir/tool.sh' << 'EOF'
454 > #!/bin/sh
454 > #!/bin/sh
455 > chmod -x $2/a
455 > chmod -x $2/a
456 > echo "** custom diff **"
456 > echo "** custom diff **"
457 > EOF
457 > EOF
458
458
459 $ hg --debug tl --config extdiff.tl= --config merge-tools.tl.executable=$tool
459 $ hg --debug tl --config extdiff.tl= --config merge-tools.tl.executable=$tool
460 making snapshot of 2 files from rev * (glob)
460 making snapshot of 2 files from rev * (glob)
461 a
461 a
462 b
462 b
463 making snapshot of 2 files from working directory
463 making snapshot of 2 files from working directory
464 a
464 a
465 b
465 b
466 running '$TESTTMP/a/dir/tool.sh a.* a' in */extdiff.* (glob)
466 running '$TESTTMP/a/dir/tool.sh a.* a' in */extdiff.* (glob)
467 ** custom diff **
467 ** custom diff **
468 file changed while diffing. Overwriting: $TESTTMP/a/a (src: */extdiff.*/a/a) (glob)
468 file changed while diffing. Overwriting: $TESTTMP/a/a (src: */extdiff.*/a/a) (glob)
469 cleaning up temp directory
469 cleaning up temp directory
470 [1]
470 [1]
471
471
472 $ [ -x a ]
472 $ [ -x a ]
473 [1]
473 [1]
474 #endif
474 #endif
475
475
476 $ cd ..
476 $ cd ..
477
477
478 #if symlink
478 #if symlink
479
479
480 Test symlinks handling (issue1909)
480 Test symlinks handling (issue1909)
481
481
482 $ hg init testsymlinks
482 $ hg init testsymlinks
483 $ cd testsymlinks
483 $ cd testsymlinks
484 $ echo a > a
484 $ echo a > a
485 $ hg ci -Am adda
485 $ hg ci -Am adda
486 adding a
486 adding a
487 $ echo a >> a
487 $ echo a >> a
488 $ ln -s missing linka
488 $ ln -s missing linka
489 $ hg add linka
489 $ hg add linka
490 $ hg falabala -r 0 --traceback
490 $ hg falabala -r 0 --traceback
491 diffing testsymlinks.07f494440405 testsymlinks
491 diffing testsymlinks.07f494440405 testsymlinks
492 [1]
492 [1]
493 $ cd ..
493 $ cd ..
494
494
495 #endif
495 #endif
496
496
497 Test handling of non-ASCII paths in generated docstrings (issue5301)
497 Test handling of non-ASCII paths in generated docstrings (issue5301)
498
498
499 >>> with open("u", "wb") as f:
499 >>> with open("u", "wb") as f:
500 ... n = f.write(b"\xa5\xa5")
500 ... n = f.write(b"\xa5\xa5")
501 $ U=`cat u`
501 $ U=`cat u`
502
502
503 $ HGPLAIN=1 hg --config hgext.extdiff= --config extdiff.cmd.td=hi help -k xyzzy
503 $ HGPLAIN=1 hg --config hgext.extdiff= --config extdiff.cmd.td=hi help -k xyzzy
504 abort: no matches
504 abort: no matches
505 (try 'hg help' for a list of topics)
505 (try 'hg help' for a list of topics)
506 [255]
506 [255]
507
507
508 $ HGPLAIN=1 hg --config hgext.extdiff= --config extdiff.cmd.td=hi help td > /dev/null
508 $ HGPLAIN=1 hg --config hgext.extdiff= --config extdiff.cmd.td=hi help td > /dev/null
509
509
510 $ LC_MESSAGES=ja_JP.UTF-8 hg --config hgext.extdiff= --config extdiff.cmd.td=$U help -k xyzzy
510 $ LC_MESSAGES=ja_JP.UTF-8 hg --config hgext.extdiff= --config extdiff.cmd.td=$U help -k xyzzy
511 abort: no matches
511 abort: no matches
512 (try 'hg help' for a list of topics)
512 (try 'hg help' for a list of topics)
513 [255]
513 [255]
514
514
515 $ LC_MESSAGES=ja_JP.UTF-8 hg --config hgext.extdiff= --config extdiff.cmd.td=$U help td \
515 $ LC_MESSAGES=ja_JP.UTF-8 hg --config hgext.extdiff= --config extdiff.cmd.td=$U help td \
516 > | grep "^ '"
516 > | grep "^ '"
517 '\xa5\xa5'
517 '\xa5\xa5'
518
519 $ cd $TESTTMP
520
521 Test that diffing a single file works, even if that file is new
522
523 $ hg init testsinglefile
524 $ cd testsinglefile
525 $ echo a > a
526 $ hg add a
527 $ hg falabala
528 diffing * */a (glob)
529 [1]
530 $ hg ci -qm a
531 $ hg falabala -c .
532 diffing * */a (glob)
533 [1]
534 $ echo a >> a
535 $ hg falabala
536 diffing */a */a (glob)
537 [1]
538 $ hg ci -qm 2a
539 $ hg falabala -c .
540 diffing */a */a (glob)
541 [1]
General Comments 0
You need to be logged in to leave comments. Login now