If both -R and -D are specified when updating, remove all untracked directories 

This is required to avoid the need to clobber the bots when moving a directory 
to deps/. Currently, the directory in question is likely to remain in the 
working copy, despite having been removed, due to the presence of untracked 
files. This causes the checkout from deps/ to fail. 

With this change, when both --reset and --delete_unversioned_trees are 
specified, the the directory in question will be removed from the working copy, 
thereby allowing the copy in deps/ to be checked out correctly. 

Note that untracked directories which are explicitly ignored (ie in .gitignore 
or svn:ignore) will not be removed. 

Note that this was previously landed in http://codereview.chromium.org/9348054 
but reverted due to problems with symlinks in the chromeos build. 

BUG=112887, chromium-os:20759

Review URL: http://codereview.chromium.org/9404014

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@122300 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/gclient_scm.py b/gclient_scm.py
index 3544101..881b7f0 100644
--- a/gclient_scm.py
+++ b/gclient_scm.py
@@ -442,6 +442,22 @@
     if verbose:
       print('Checked out revision %s' % self.revinfo(options, (), None))
 
+    # If --reset and --delete_unversioned_trees are specified, remove any
+    # untracked directories.
+    if options.reset and options.delete_unversioned_trees:
+      # GIT.CaptureStatus() uses 'dit diff' to compare to a specific SHA1 (the
+      # merge-base by default), so doesn't include untracked files. So we use
+      # 'git ls-files --directory --others --exclude-standard' here directly.
+      paths = scm.GIT.Capture(
+          ['ls-files', '--directory', '--others', '--exclude-standard'],
+          self.checkout_path)
+      for path in (p for p in paths.splitlines() if p.endswith('/')):
+        full_path = os.path.join(self.checkout_path, path)
+        if not os.path.islink(full_path):
+          print('\n_____ removing unversioned directory %s' % path)
+          gclient_utils.RemoveDirectory(full_path)
+
+
   def revert(self, options, args, file_list):
     """Reverts local modifications.
 
@@ -922,7 +938,7 @@
         if not options.force and not options.reset:
           # Look for local modifications but ignore unversioned files.
           for status in scm.SVN.CaptureStatus(None, self.checkout_path):
-            if status[0] != '?':
+            if status[0][0] != '?':
               raise gclient_utils.Error(
                   ('Can\'t switch the checkout to %s; UUID don\'t match and '
                    'there is local changes in %s. Delete the directory and '
@@ -941,11 +957,21 @@
     if not options.force and str(from_info['Revision']) == revision:
       if options.verbose or not forced_revision:
         print('\n_____ %s%s' % (self.relpath, rev_str))
-      return
+    else:
+      command = ['update', self.checkout_path]
+      command = self._AddAdditionalUpdateFlags(command, options, revision)
+      self._RunAndGetFileList(command, options, file_list, self._root_dir)
 
-    command = ['update', self.checkout_path]
-    command = self._AddAdditionalUpdateFlags(command, options, revision)
-    self._RunAndGetFileList(command, options, file_list, self._root_dir)
+    # If --reset and --delete_unversioned_trees are specified, remove any
+    # untracked files and directories.
+    if options.reset and options.delete_unversioned_trees:
+      for status in scm.SVN.CaptureStatus(None, self.checkout_path):
+        full_path = os.path.join(self.checkout_path, status[1])
+        if (status[0][0] == '?'
+            and os.path.isdir(full_path)
+            and not os.path.islink(full_path)):
+          print('\n_____ removing unversioned directory %s' % status[1])
+          gclient_utils.RemoveDirectory(full_path)
 
   def updatesingle(self, options, args, file_list):
     filename = args.pop()