diff --git a/gclient.py b/gclient.py index 2b8ae3564..f2d90f692 100755 --- a/gclient.py +++ b/gclient.py @@ -1563,6 +1563,103 @@ it or fix the checkout. patch_refs[patch_repo] = patch_ref return patch_refs, target_branches + def _RemoveUnversionedGitDirs(self): + """Remove directories that are no longer part of the checkout. + + Notify the user if there is an orphaned entry in their working copy. + Only delete the directory if there are no changes in it, and + delete_unversioned_trees is set to true. + """ + + entries = [i.name for i in self.root.subtree(False) if i.url] + full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep)) + for e in entries] + + for entry, prev_url in self._ReadEntries().iteritems(): + if not prev_url: + # entry must have been overridden via .gclient custom_deps + continue + # Fix path separator on Windows. + entry_fixed = entry.replace('/', os.path.sep) + e_dir = os.path.join(self.root_dir, entry_fixed) + # Use entry and not entry_fixed there. + if (entry not in entries and + (not any(path.startswith(entry + '/') for path in entries)) and + os.path.exists(e_dir)): + # The entry has been removed from DEPS. + scm = gclient_scm.GitWrapper( + prev_url, self.root_dir, entry_fixed, self.outbuf) + + # Check to see if this directory is now part of a higher-up checkout. + scm_root = None + try: + scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path) + except subprocess2.CalledProcessError: + pass + if not scm_root: + logging.warning('Could not find checkout root for %s. Unable to ' + 'determine whether it is part of a higher-level ' + 'checkout, so not removing.' % entry) + continue + + # This is to handle the case of third_party/WebKit migrating from + # being a DEPS entry to being part of the main project. + # If the subproject is a Git project, we need to remove its .git + # folder. Otherwise git operations on that folder will have different + # effects depending on the current working directory. + if os.path.abspath(scm_root) == os.path.abspath(e_dir): + e_par_dir = os.path.join(e_dir, os.pardir) + if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir): + par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir) + # rel_e_dir : relative path of entry w.r.t. its parent repo. + rel_e_dir = os.path.relpath(e_dir, par_scm_root) + if gclient_scm.scm.GIT.IsDirectoryVersioned( + par_scm_root, rel_e_dir): + save_dir = scm.GetGitBackupDirPath() + # Remove any eventual stale backup dir for the same project. + if os.path.exists(save_dir): + gclient_utils.rmtree(save_dir) + os.rename(os.path.join(e_dir, '.git'), save_dir) + # When switching between the two states (entry/ is a subproject + # -> entry/ is part of the outer project), it is very likely + # that some files are changed in the checkout, unless we are + # jumping *exactly* across the commit which changed just DEPS. + # In such case we want to cleanup any eventual stale files + # (coming from the old subproject) in order to end up with a + # clean checkout. + gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir) + assert not os.path.exists(os.path.join(e_dir, '.git')) + print(('\nWARNING: \'%s\' has been moved from DEPS to a higher ' + 'level checkout. The git folder containing all the local' + ' branches has been saved to %s.\n' + 'If you don\'t care about its state you can safely ' + 'remove that folder to free up space.') % + (entry, save_dir)) + continue + + if scm_root in full_entries: + logging.info('%s is part of a higher level checkout, not removing', + scm.GetCheckoutRoot()) + continue + + file_list = [] + scm.status(self._options, [], file_list) + modified_files = file_list != [] + if (not self._options.delete_unversioned_trees or + (modified_files and not self._options.force)): + # There are modified files in this entry. Keep warning until + # removed. + print(('\nWARNING: \'%s\' is no longer part of this client. ' + 'It is recommended that you manually remove it.\n') % + entry_fixed) + else: + # Delete the entry + print('\n________ deleting \'%s\' in \'%s\'' % ( + entry_fixed, self.root_dir)) + gclient_utils.rmtree(e_dir) + # record the current list of entries for next time + self._SaveEntries() + def RunOnDeps(self, command, args, ignore_requirements=False, progress=True): """Runs a command on each dependency in a client and its dependencies. @@ -1624,103 +1721,7 @@ it or fix the checkout. if gn_args_dep and gn_args_dep.HasGNArgsFile(): gn_args_dep.WriteGNArgsFile() - if not self._options.nohooks: - if should_show_progress: - pm = Progress('Running hooks', 1) - self.RunHooksRecursively(self._options, pm) - - if command == 'update': - # Notify the user if there is an orphaned entry in their working copy. - # Only delete the directory if there are no changes in it, and - # delete_unversioned_trees is set to true. - entries = [i.name for i in self.root.subtree(False) if i.url] - full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep)) - for e in entries] - - for entry, prev_url in self._ReadEntries().iteritems(): - if not prev_url: - # entry must have been overridden via .gclient custom_deps - continue - # Fix path separator on Windows. - entry_fixed = entry.replace('/', os.path.sep) - e_dir = os.path.join(self.root_dir, entry_fixed) - # Use entry and not entry_fixed there. - if (entry not in entries and - (not any(path.startswith(entry + '/') for path in entries)) and - os.path.exists(e_dir)): - # The entry has been removed from DEPS. - scm = gclient_scm.GitWrapper( - prev_url, self.root_dir, entry_fixed, self.outbuf) - - # Check to see if this directory is now part of a higher-up checkout. - scm_root = None - try: - scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path) - except subprocess2.CalledProcessError: - pass - if not scm_root: - logging.warning('Could not find checkout root for %s. Unable to ' - 'determine whether it is part of a higher-level ' - 'checkout, so not removing.' % entry) - continue - - # This is to handle the case of third_party/WebKit migrating from - # being a DEPS entry to being part of the main project. - # If the subproject is a Git project, we need to remove its .git - # folder. Otherwise git operations on that folder will have different - # effects depending on the current working directory. - if os.path.abspath(scm_root) == os.path.abspath(e_dir): - e_par_dir = os.path.join(e_dir, os.pardir) - if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir): - par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir) - # rel_e_dir : relative path of entry w.r.t. its parent repo. - rel_e_dir = os.path.relpath(e_dir, par_scm_root) - if gclient_scm.scm.GIT.IsDirectoryVersioned( - par_scm_root, rel_e_dir): - save_dir = scm.GetGitBackupDirPath() - # Remove any eventual stale backup dir for the same project. - if os.path.exists(save_dir): - gclient_utils.rmtree(save_dir) - os.rename(os.path.join(e_dir, '.git'), save_dir) - # When switching between the two states (entry/ is a subproject - # -> entry/ is part of the outer project), it is very likely - # that some files are changed in the checkout, unless we are - # jumping *exactly* across the commit which changed just DEPS. - # In such case we want to cleanup any eventual stale files - # (coming from the old subproject) in order to end up with a - # clean checkout. - gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir) - assert not os.path.exists(os.path.join(e_dir, '.git')) - print(('\nWARNING: \'%s\' has been moved from DEPS to a higher ' - 'level checkout. The git folder containing all the local' - ' branches has been saved to %s.\n' - 'If you don\'t care about its state you can safely ' - 'remove that folder to free up space.') % - (entry, save_dir)) - continue - - if scm_root in full_entries: - logging.info('%s is part of a higher level checkout, not removing', - scm.GetCheckoutRoot()) - continue - - file_list = [] - scm.status(self._options, [], file_list) - modified_files = file_list != [] - if (not self._options.delete_unversioned_trees or - (modified_files and not self._options.force)): - # There are modified files in this entry. Keep warning until - # removed. - print(('\nWARNING: \'%s\' is no longer part of this client. ' - 'It is recommended that you manually remove it.\n') % - entry_fixed) - else: - # Delete the entry - print('\n________ deleting \'%s\' in \'%s\'' % ( - entry_fixed, self.root_dir)) - gclient_utils.rmtree(e_dir) - # record the current list of entries for next time - self._SaveEntries() + self._RemoveUnversionedGitDirs() # Sync CIPD dependencies once removed deps are deleted. In case a git # dependency was moved to CIPD, we want to remove the old git directory @@ -1728,6 +1729,12 @@ it or fix the checkout. if self._cipd_root: self._cipd_root.run(command) + if not self._options.nohooks: + if should_show_progress: + pm = Progress('Running hooks', 1) + self.RunHooksRecursively(self._options, pm) + + return 0 def PrintRevInfo(self): diff --git a/testing_support/fake_repos.py b/testing_support/fake_repos.py index e0e2147dc..493cf4174 100755 --- a/testing_support/fake_repos.py +++ b/testing_support/fake_repos.py @@ -756,7 +756,12 @@ deps = { ], 'dep_type': 'cipd', }, -}""", +} +hooks = [{ + # make sure src/repo12 exists and is a CIPD dir. + 'action': ['python', '-c', 'with open("src/repo12/_cipd"): pass'], +}] +""", 'origin': 'git/repo_13@3\n' })