Merge "On project cleanup, don't remove nested projects"
diff --git a/subcmds/sync.py b/subcmds/sync.py
index ecf2ffc..cc0b17e 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -457,6 +457,59 @@
     else:
       self.manifest._Unload()
 
+  def _DeleteProject(self, path):
+    print('Deleting obsolete path %s' % path, file=sys.stderr)
+
+    # Delete the .git directory first, so we're less likely to have a partially
+    # working git repository around. There shouldn't be any git projects here,
+    # so rmtree works.
+    try:
+      shutil.rmtree(os.path.join(path, '.git'))
+    except OSError:
+      print('Failed to remove %s' % os.path.join(path, '.git'), file=sys.stderr)
+      print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
+      print('       remove manually, then run sync again', file=sys.stderr)
+      return -1
+
+    # Delete everything under the worktree, except for directories that contain
+    # another git project
+    dirs_to_remove = []
+    failed = False
+    for root, dirs, files in os.walk(path):
+      for f in files:
+        try:
+          os.remove(os.path.join(root, f))
+        except OSError:
+          print('Failed to remove %s' % os.path.join(root, f), file=sys.stderr)
+          failed = True
+      dirs[:] = [d for d in dirs
+                 if not os.path.lexists(os.path.join(root, d, '.git'))]
+      dirs_to_remove += [os.path.join(root, d) for d in dirs
+                         if os.path.join(root, d) not in dirs_to_remove]
+    for d in reversed(dirs_to_remove):
+      if len(os.listdir(d)) == 0:
+        try:
+          os.rmdir(d)
+        except OSError:
+          print('Failed to remove %s' % os.path.join(root, d), file=sys.stderr)
+          failed = True
+          continue
+    if failed:
+      print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
+      print('       remove manually, then run sync again', file=sys.stderr)
+      return -1
+
+    # Try deleting parent dirs if they are empty
+    project_dir = path
+    while project_dir != self.manifest.topdir:
+      if len(os.listdir(project_dir)) == 0:
+        os.rmdir(project_dir)
+      else:
+        break
+      project_dir = os.path.dirname(project_dir)
+
+    return 0
+
   def UpdateProjectList(self):
     new_project_paths = []
     for project in self.GetProjects(None, missing_ok=True):
@@ -477,8 +530,8 @@
           continue
         if path not in new_project_paths:
           # If the path has already been deleted, we don't need to do it
-          if os.path.exists(self.manifest.topdir + '/' + path):
-            gitdir = os.path.join(self.manifest.topdir, path, '.git')
+          gitdir = os.path.join(self.manifest.topdir, path, '.git')
+          if os.path.exists(gitdir):
             project = Project(
                            manifest = self.manifest,
                            name = path,
@@ -497,18 +550,8 @@
               print('       commit changes, then run sync again',
                     file=sys.stderr)
               return -1
-            else:
-              print('Deleting obsolete path %s' % project.worktree,
-                    file=sys.stderr)
-              shutil.rmtree(project.worktree)
-              # Try deleting parent subdirs if they are empty
-              project_dir = os.path.dirname(project.worktree)
-              while project_dir != self.manifest.topdir:
-                try:
-                  os.rmdir(project_dir)
-                except OSError:
-                  break
-                project_dir = os.path.dirname(project_dir)
+            elif self._DeleteProject(project.worktree):
+              return -1
 
     new_project_paths.sort()
     fd = open(file_path, 'w')