]>
Tony Duckles's Git Repositories (git.nynim.org) - svn2svn.git/blob - svn2svn/shell.py
1 """ Shell functions """
4 from svn2svn
.errors
import ExternalCommandFailed
8 from datetime
import datetime
10 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 locale_encoding
= locale
.getpreferredencoding()
78 return locale_encoding
83 # If showing OS commands being executed, don't wrap "safe" strings in quotes.
84 if re
.compile('^[A-Za-z0-9=-]+$').match(s
):
90 return q
+ s
.replace('\\', '\\\\').replace("'", "'\"'\"'") + q
92 def _run_raw_command(cmd
, args
, fail_if_stderr
=False):
93 cmd_string
= "%s %s" % (cmd
, " ".join(map(shell_quote
, args
)))
94 ui
.status("* %s", cmd_string
, level
=ui
.DEBUG
)
96 pipe
= Popen([cmd
] + args
, executable
=cmd
, stdout
=PIPE
, stderr
=PIPE
)
98 etype
, value
= sys
.exc_info()[:2]
99 raise ExternalCommandFailed(
100 "Failed running external program: %s\nError: %s"
101 % (cmd_string
, "".join(traceback
.format_exception_only(etype
, value
))))
102 out
, err
= pipe
.communicate()
103 if "nothing changed" == out
.strip(): # skip this error
105 if pipe
.returncode
!= 0 or (fail_if_stderr
and err
.strip()):
106 raise ExternalCommandFailed(
107 "External program failed (return code %d): %s\n%s\n%s"
108 % (pipe
.returncode
, cmd_string
, err
, out
))
111 def _run_raw_shell_command(cmd
):
112 ui
.status("* %s", cmd
, level
=ui
.DEBUG
)
113 st
, out
= commands
.getstatusoutput(cmd
)
115 raise ExternalCommandFailed(
116 "External program failed with non-zero return code (%d): %s\n%s"
120 def run_command(cmd
, args
=None, bulk_args
=None, encoding
=None, fail_if_stderr
=False):
122 Run a command without using the shell.
125 bulk_args
= bulk_args
or []
126 def _transform_arg(a
):
127 if isinstance(a
, unicode):
128 a
= a
.encode(encoding
or locale_encoding
or 'UTF-8')
129 elif not isinstance(a
, str):
133 cmd
= find_program(cmd
)
135 return _run_raw_command(cmd
, map(_transform_arg
, args
), fail_if_stderr
)
136 # If one of bulk_args starts with a dash (e.g. '-foo.php'),
137 # svn will take this as an option. Adding '--' ends the search for
140 if a
.strip().startswith('-'):
146 while i
< len(bulk_args
):
147 stop
= i
+ max_args_num
- len(args
)
149 for a
in bulk_args
[i
:stop
]:
150 sub_args
.append(_transform_arg(a
))
151 out
+= _run_raw_command(cmd
, args
+ sub_args
, fail_if_stderr
)
155 def run_shell_command(cmd
, args
=None, bulk_args
=None, encoding
=None):
157 Run a shell command, properly quoting and encoding arguments.
158 Probably only works on Un*x-like systems.
161 if isinstance(a
, unicode):
162 a
= a
.encode(encoding
or locale_encoding
)
163 elif not isinstance(a
, str):
165 return shell_quote(a
)
168 cmd
+= " " + " ".join(_quote_arg(a
) for a
in args
)
173 return _run_raw_shell_command(cmd
)
174 while i
< len(bulk_args
):
175 stop
= i
+ max_args_num
- len(args
)
177 for a
in bulk_args
[i
:stop
]:
178 sub_args
.append(_quote_arg(a
))
179 sub_cmd
= cmd
+ " " + " ".join(sub_args
)
180 out
+= _run_raw_shell_command(sub_cmd
)
184 def run_svn(args
=None, bulk_args
=None, fail_if_stderr
=False,
187 Run an SVN command, returns the (bytes) output.
190 # The @ sign in Subversion revers to a pegged revision number.
191 # SVN treats files with @ in the filename a bit special.
192 # See: http://stackoverflow.com/questions/1985203
193 for idx
in range(len(args
)):
194 if "@" in args
[idx
] and args
[idx
][0] not in ("-", '"'):
195 args
[idx
] = "%s@" % args
[idx
]
197 for idx
in range(len(bulk_args
)):
198 if ("@" in bulk_args
[idx
]
199 and bulk_args
[idx
][0] not in ("-", '"')):
200 bulk_args
[idx
] = "%s@" % bulk_args
[idx
]
201 return run_command("svn",
202 args
=args
, bulk_args
=bulk_args
, fail_if_stderr
=fail_if_stderr
)
204 def skip_dirs(paths
, basedir
="."):
206 Skip all directories from path list, including symbolic links to real dirs.
208 # NOTE: both tests are necessary (Cameron Hutchison's patch for symbolic
209 # links to directories)
210 return [p
for p
in paths
211 if not os
.path
.isdir(os
.path
.join(basedir
, p
))
212 or os
.path
.islink(os
.path
.join(basedir
, p
))]
214 def get_script_name():
215 """Helper to return the name of the command line script that was called."""
216 return os
.path
.basename(sys
.argv
[0])