gclient: remove wildcard import from git_scm

Part of a larger refactoring to abstract SCM-specific bits.

presubmit_support, revert, gcl: modify to import gclient_scm and gclient_utils

Part of a larger refactoring to abstract SCM-specific bits.

revert, gcl: modify to import gclient_scm and gclient_utils

Part of a larger refactoring to abstract SCM-specific bits.

gclient: pull out SCM bits

Pulled out SCMWrapper into gcliet_scm.py as part of a larger refactoring to
abstract SCM-specific bits. Plan is to evenutally add git support.

Pulling out SCMWrapper also required pulling out utility functions into
a gclient_utility.py.

Patch contributed by msb@

TEST=none
BUG=none


git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@26423 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/gclient_utils.py b/gclient_utils.py
new file mode 100644
index 0000000..ff8a240
--- /dev/null
+++ b/gclient_utils.py
@@ -0,0 +1,239 @@
+# Copyright 2009 Google Inc.  All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import subprocess
+import sys
+import xml.dom.minidom
+
+## Generic utils
+
+
+def ParseXML(output):
+  try:
+    return xml.dom.minidom.parseString(output)
+  except xml.parsers.expat.ExpatError:
+    return None
+
+
+def GetNamedNodeText(node, node_name):
+  child_nodes = node.getElementsByTagName(node_name)
+  if not child_nodes:
+    return None
+  assert len(child_nodes) == 1 and child_nodes[0].childNodes.length == 1
+  return child_nodes[0].firstChild.nodeValue
+
+
+def GetNodeNamedAttributeText(node, node_name, attribute_name):
+  child_nodes = node.getElementsByTagName(node_name)
+  if not child_nodes:
+    return None
+  assert len(child_nodes) == 1
+  return child_nodes[0].getAttribute(attribute_name)
+
+
+class Error(Exception):
+  """gclient exception class."""
+  pass
+
+
+class PrintableObject(object):
+  def __str__(self):
+    output = ''
+    for i in dir(self):
+      if i.startswith('__'):
+        continue
+      output += '%s = %s\n' % (i, str(getattr(self, i, '')))
+    return output
+
+
+def FileRead(filename):
+  content = None
+  f = open(filename, "rU")
+  try:
+    content = f.read()
+  finally:
+    f.close()
+  return content
+
+
+def FileWrite(filename, content):
+  f = open(filename, "w")
+  try:
+    f.write(content)
+  finally:
+    f.close()
+
+
+def RemoveDirectory(*path):
+  """Recursively removes a directory, even if it's marked read-only.
+
+  Remove the directory located at *path, if it exists.
+
+  shutil.rmtree() doesn't work on Windows if any of the files or directories
+  are read-only, which svn repositories and some .svn files are.  We need to
+  be able to force the files to be writable (i.e., deletable) as we traverse
+  the tree.
+
+  Even with all this, Windows still sometimes fails to delete a file, citing
+  a permission error (maybe something to do with antivirus scans or disk
+  indexing).  The best suggestion any of the user forums had was to wait a
+  bit and try again, so we do that too.  It's hand-waving, but sometimes it
+  works. :/
+
+  On POSIX systems, things are a little bit simpler.  The modes of the files
+  to be deleted doesn't matter, only the modes of the directories containing
+  them are significant.  As the directory tree is traversed, each directory
+  has its mode set appropriately before descending into it.  This should
+  result in the entire tree being removed, with the possible exception of
+  *path itself, because nothing attempts to change the mode of its parent.
+  Doing so would be hazardous, as it's not a directory slated for removal.
+  In the ordinary case, this is not a problem: for our purposes, the user
+  will never lack write permission on *path's parent.
+  """
+  file_path = os.path.join(*path)
+  if not os.path.exists(file_path):
+    return
+
+  if os.path.islink(file_path) or not os.path.isdir(file_path):
+    raise Error("RemoveDirectory asked to remove non-directory %s" % file_path)
+
+  has_win32api = False
+  if sys.platform == 'win32':
+    has_win32api = True
+    # Some people don't have the APIs installed. In that case we'll do without.
+    try:
+      win32api = __import__('win32api')
+      win32con = __import__('win32con')
+    except ImportError:
+      has_win32api = False
+  else:
+    # On POSIX systems, we need the x-bit set on the directory to access it,
+    # the r-bit to see its contents, and the w-bit to remove files from it.
+    # The actual modes of the files within the directory is irrelevant.
+    os.chmod(file_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
+  for fn in os.listdir(file_path):
+    fullpath = os.path.join(file_path, fn)
+
+    # If fullpath is a symbolic link that points to a directory, isdir will
+    # be True, but we don't want to descend into that as a directory, we just
+    # want to remove the link.  Check islink and treat links as ordinary files
+    # would be treated regardless of what they reference.
+    if os.path.islink(fullpath) or not os.path.isdir(fullpath):
+      if sys.platform == 'win32':
+        os.chmod(fullpath, stat.S_IWRITE)
+        if has_win32api:
+          win32api.SetFileAttributes(fullpath, win32con.FILE_ATTRIBUTE_NORMAL)
+      try:
+        os.remove(fullpath)
+      except OSError, e:
+        if e.errno != errno.EACCES or sys.platform != 'win32':
+          raise
+        print 'Failed to delete %s: trying again' % fullpath
+        time.sleep(0.1)
+        os.remove(fullpath)
+    else:
+      RemoveDirectory(fullpath)
+
+  if sys.platform == 'win32':
+    os.chmod(file_path, stat.S_IWRITE)
+    if has_win32api:
+      win32api.SetFileAttributes(file_path, win32con.FILE_ATTRIBUTE_NORMAL)
+  try:
+    os.rmdir(file_path)
+  except OSError, e:
+    if e.errno != errno.EACCES or sys.platform != 'win32':
+      raise
+    print 'Failed to remove %s: trying again' % file_path
+    time.sleep(0.1)
+    os.rmdir(file_path)
+
+
+def SubprocessCall(command, in_directory, fail_status=None):
+  """Runs command, a list, in directory in_directory.
+
+  This function wraps SubprocessCallAndFilter, but does not perform the
+  filtering functions.  See that function for a more complete usage
+  description.
+  """
+  # Call subprocess and capture nothing:
+  SubprocessCallAndFilter(command, in_directory, True, True, fail_status)
+
+
+def SubprocessCallAndFilter(command,
+                            in_directory,
+                            print_messages,
+                            print_stdout,
+                            fail_status=None, filter=None):
+  """Runs command, a list, in directory in_directory.
+
+  If print_messages is true, a message indicating what is being done
+  is printed to stdout. If print_stdout is true, the command's stdout
+  is also forwarded to stdout.
+
+  If a filter function is specified, it is expected to take a single
+  string argument, and it will be called with each line of the
+  subprocess's output. Each line has had the trailing newline character
+  trimmed.
+
+  If the command fails, as indicated by a nonzero exit status, gclient will
+  exit with an exit status of fail_status.  If fail_status is None (the
+  default), gclient will raise an Error exception.
+  """
+
+  if print_messages:
+    print("\n________ running \'%s\' in \'%s\'"
+          % (' '.join(command), in_directory))
+
+  # *Sigh*:  Windows needs shell=True, or else it won't search %PATH% for the
+  # executable, but shell=True makes subprocess on Linux fail when it's called
+  # with a list because it only tries to execute the first item in the list.
+  kid = subprocess.Popen(command, bufsize=0, cwd=in_directory,
+      shell=(sys.platform == 'win32'), stdout=subprocess.PIPE)
+
+  # Also, we need to forward stdout to prevent weird re-ordering of output.
+  # This has to be done on a per byte basis to make sure it is not buffered:
+  # normally buffering is done for each line, but if svn requests input, no
+  # end-of-line character is output after the prompt and it would not show up.
+  in_byte = kid.stdout.read(1)
+  in_line = ""
+  while in_byte:
+    if in_byte != "\r":
+      if print_stdout:
+        sys.stdout.write(in_byte)
+      if in_byte != "\n":
+        in_line += in_byte
+    if in_byte == "\n" and filter:
+      filter(in_line)
+      in_line = ""
+    in_byte = kid.stdout.read(1)
+  rv = kid.wait()
+
+  if rv:
+    msg = "failed to run command: %s" % " ".join(command)
+
+    if fail_status != None:
+      print >>sys.stderr, msg
+      sys.exit(fail_status)
+
+    raise Error(msg)
+
+
+def IsUsingGit(root, paths):
+  """Returns True if we're using git to manage any of our checkouts.
+  |entries| is a list of paths to check."""
+  for path in paths:
+    if os.path.exists(os.path.join(root, path, '.git')):
+      return True
+  return False