]>
Tony Duckles's Git Repositories (git.nynim.org) - svn2svn.git/blob - svn2svn/shell.py
1 """ Shell functions """
4 from errors
import ExternalCommandFailed
14 from datetime
import datetime
15 from subprocess
import Popen
, PIPE
, STDOUT
23 # Windows compatibility code by Bill Baxter
25 def find_program(name
):
27 Find the name of the program for Popen.
28 Windows is finnicky about having the complete file name. Popen
29 won't search the %PATH% for you automatically.
30 (Adapted from ctypes.find_library)
32 # See MSDN for the REAL search order.
33 base
, ext
= os
.path
.splitext(name
)
37 exts
= ['.bat', '.exe']
38 for directory
in os
.environ
['PATH'].split(os
.pathsep
):
40 fname
= os
.path
.join(directory
, base
+ e
)
41 if os
.path
.exists(fname
):
45 def find_program(name
):
47 Find the name of the program for Popen.
48 On Unix, popen isn't picky about having absolute paths.
53 def _rmtree_error_handler(func
, path
, exc_info
):
55 Error handler for rmtree. Helps removing the read-only protection under
56 Windows (and others?).
57 Adapted from http://www.proaxis.com/~darkwing/hot-backup.py
58 and http://patchwork.ozlabs.org/bazaar-ng/patch?id=4243
60 if func
in (os
.remove
, os
.rmdir
) and os
.path
.exists(path
):
61 # Change from read-only to writeable
62 os
.chmod(path
, os
.stat(path
).st_mode | stat
.S_IWRITE
)
65 # something else must be wrong...
70 Wrapper around shutil.rmtree(), to provide more error-resistent behaviour.
72 return shutil
.rmtree(path
, False, _rmtree_error_handler
)
75 # Make sure we do not get localized output from the Subversion
76 # command line client.
77 os
.environ
['LC_MESSAGES'] = 'C'
79 locale_encoding
= locale
.getpreferredencoding()
82 return locale_encoding
85 # No need to wrap "safe" strings in quotes
86 if re
.compile('^[A-Za-z0-9=-]+$').match(s
):
92 return q
+ s
.replace('\\', '\\\\').replace("'", "'\"'\"'") + q
94 def _run_raw_command(cmd
, args
, fail_if_stderr
=False, no_fail
=False):
95 cmd_string
= "%s %s" % (cmd
, " ".join(map(shell_quote
, args
)))
97 if cmd
== 'svn' and args
[0] in ['status', 'st', 'log', 'info', 'list', 'proplist', 'propget', 'update', 'up', 'cleanup', 'revert']:
98 # Show status-only commands (commands which make no changes to WC) in dim-blue
100 ui
.status("$ %s", cmd_string
, level
=ui
.EXTRA
, color
=color
)
102 pipe
= Popen([cmd
] + args
, executable
=cmd
, stdout
=PIPE
, stderr
=PIPE
)
104 etype
, value
= sys
.exc_info()[:2]
105 raise ExternalCommandFailed(
106 "Failed running external program: %s\nError: %s"
107 % (cmd_string
, "".join(traceback
.format_exception_only(etype
, value
))))
108 out
, err
= pipe
.communicate()
109 if "nothing changed" == out
.strip(): # skip this error
111 if (pipe
.returncode
!= 0 or (fail_if_stderr
and err
.strip())) and not no_fail
:
112 raise ExternalCommandFailed(
113 "External program failed (return code %d): %s\n%s\n%s"
114 % (pipe
.returncode
, cmd_string
, err
, out
))
117 def _run_raw_shell_command(cmd
, no_fail
=False):
118 ui
.status("* %s", cmd
, level
=ui
.EXTRA
, color
='BLUE')
119 st
, out
= commands
.getstatusoutput(cmd
)
120 if st
!= 0 and not no_fail
:
121 raise ExternalCommandFailed(
122 "External program failed with non-zero return code (%d): %s\n%s"
126 def run_command(cmd
, args
=None, bulk_args
=None, encoding
=None, fail_if_stderr
=False, no_fail
=False):
128 Run a command without using the shell.
131 bulk_args
= bulk_args
or []
132 def _transform_arg(a
):
133 if isinstance(a
, unicode):
134 a
= a
.encode(encoding
or locale_encoding
or 'UTF-8')
135 elif not isinstance(a
, str):
139 cmd
= find_program(cmd
)
141 return _run_raw_command(cmd
, map(_transform_arg
, args
), fail_if_stderr
, no_fail
)
142 # If one of bulk_args starts with a dash (e.g. '-foo.php'),
143 # svn will take this as an option. Adding '--' ends the search for
146 if a
.strip().startswith('-'):
152 while i
< len(bulk_args
):
153 stop
= i
+ max_args_num
- len(args
)
155 for a
in bulk_args
[i
:stop
]:
156 sub_args
.append(_transform_arg(a
))
157 out
+= _run_raw_command(cmd
, args
+ sub_args
, fail_if_stderr
, no_fail
)
161 def run_shell_command(cmd
, args
=None, bulk_args
=None, encoding
=None, no_fail
=False):
163 Run a shell command, properly quoting and encoding arguments.
164 Probably only works on Un*x-like systems.
167 if isinstance(a
, unicode):
168 a
= a
.encode(encoding
or locale_encoding
)
169 elif not isinstance(a
, str):
171 return shell_quote(a
)
174 cmd
+= " " + " ".join(_quote_arg(a
) for a
in args
)
179 return _run_raw_shell_command(cmd
, no_fail
)
180 while i
< len(bulk_args
):
181 stop
= i
+ max_args_num
- len(args
)
183 for a
in bulk_args
[i
:stop
]:
184 sub_args
.append(_quote_arg(a
))
185 sub_cmd
= cmd
+ " " + " ".join(sub_args
)
186 out
+= _run_raw_shell_command(sub_cmd
, no_fail
)
190 def run_svn(args
=None, bulk_args
=None, fail_if_stderr
=False,
191 mask_atsign
=False, no_fail
=False):
193 Run an SVN command, returns the (bytes) output.
196 # The @ sign in Subversion revers to a pegged revision number.
197 # SVN treats files with @ in the filename a bit special.
198 # See: http://stackoverflow.com/questions/1985203
199 for idx
in range(len(args
)):
200 if "@" in args
[idx
] and args
[idx
][0] not in ("-", '"'):
201 args
[idx
] = "%s@" % args
[idx
]
203 for idx
in range(len(bulk_args
)):
204 if ("@" in bulk_args
[idx
]
205 and bulk_args
[idx
][0] not in ("-", '"')):
206 bulk_args
[idx
] = "%s@" % bulk_args
[idx
]
207 return run_command("svn",
208 args
=args
, bulk_args
=bulk_args
, fail_if_stderr
=fail_if_stderr
, no_fail
=no_fail
)
210 def skip_dirs(paths
, basedir
="."):
212 Skip all directories from path list, including symbolic links to real dirs.
214 # NOTE: both tests are necessary (Cameron Hutchison's patch for symbolic
215 # links to directories)
216 return [p
for p
in paths
217 if not os
.path
.isdir(os
.path
.join(basedir
, p
))
218 or os
.path
.islink(os
.path
.join(basedir
, p
))]
220 def get_script_name():
221 """Helper to return the name of the command line script that was called."""
222 return os
.path
.basename(sys
.argv
[0])