project: Use git ls-files for DeleteWorktree file removal
Bug: 434825203 Replace filesystem walking approach with git ls-files to only delete tracked files during worktree deletion. This is more efficient and ensures only git-tracked content is removed. Changes: - Use `git ls-files -z` to get tracked files list - Remove tracked files first, then clean up empty directories - Throw DeleteWorktreeError immediately on any removal failure - Remove fallback rename logic for failed deletions Change-Id: I8d56367cd02359f23b0625788c1b9f441349af76
This commit is contained in:
parent
5d95ba8d85
commit
b825057512
1 changed files with 50 additions and 60 deletions
110
project.py
110
project.py
|
@ -1907,69 +1907,59 @@ class Project:
|
||||||
)
|
)
|
||||||
raise DeleteWorktreeError(aggregate_errors=[e])
|
raise DeleteWorktreeError(aggregate_errors=[e])
|
||||||
|
|
||||||
# Delete everything under the worktree, except for directories that
|
# Delete tracked files using git ls-files, then clean up empty dirs
|
||||||
# contain another git project.
|
# Use git ls-files to get all tracked files in the worktree
|
||||||
dirs_to_remove = []
|
try:
|
||||||
failed = False
|
files_output = self.bare_git.ls_files(
|
||||||
errors = []
|
"-z", _cwd=self.worktree
|
||||||
for root, dirs, files in platform_utils.walk(self.worktree):
|
).split("\0")
|
||||||
for f in files:
|
tracked_files = [
|
||||||
path = os.path.join(root, f)
|
f for f in files_output if f
|
||||||
|
] # Filter out empty strings
|
||||||
|
|
||||||
|
# Remove all tracked files
|
||||||
|
for rel_path in tracked_files:
|
||||||
|
abs_path = os.path.join(self.worktree, rel_path)
|
||||||
try:
|
try:
|
||||||
platform_utils.remove(path)
|
platform_utils.remove(abs_path)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if e.errno != errno.ENOENT:
|
if e.errno != errno.ENOENT:
|
||||||
logger.warning("%s: Failed to remove: %s", path, e)
|
logger.error("%s: Failed to remove: %s", abs_path, e)
|
||||||
failed = True
|
raise DeleteWorktreeError(aggregate_errors=[e])
|
||||||
errors.append(e)
|
|
||||||
dirs[:] = [
|
# Collect and remove empty directories (excluding those with .git)
|
||||||
d
|
dirs_to_remove = set()
|
||||||
for d in dirs
|
for rel_path in tracked_files:
|
||||||
if not os.path.lexists(os.path.join(root, d, ".git"))
|
# Add all parent directories of this file
|
||||||
]
|
dir_path = os.path.dirname(rel_path)
|
||||||
dirs_to_remove += [
|
while dir_path and dir_path != ".":
|
||||||
os.path.join(root, d)
|
dirs_to_remove.add(os.path.join(self.worktree, dir_path))
|
||||||
for d in dirs
|
dir_path = os.path.dirname(dir_path)
|
||||||
if os.path.join(root, d) not in dirs_to_remove
|
|
||||||
]
|
# Remove empty directories in reverse order (deepest first)
|
||||||
for d in reversed(dirs_to_remove):
|
for d in sorted(dirs_to_remove, key=len, reverse=True):
|
||||||
if platform_utils.islink(d):
|
# Skip directories that contain another git project
|
||||||
try:
|
if os.path.lexists(os.path.join(d, ".git")):
|
||||||
platform_utils.remove(d)
|
continue
|
||||||
except OSError as e:
|
|
||||||
if e.errno != errno.ENOENT:
|
if platform_utils.islink(d):
|
||||||
logger.warning("%s: Failed to remove: %s", d, e)
|
try:
|
||||||
failed = True
|
platform_utils.remove(d)
|
||||||
errors.append(e)
|
except OSError as e:
|
||||||
elif not platform_utils.listdir(d):
|
if e.errno != errno.ENOENT:
|
||||||
try:
|
logger.error("%s: Failed to remove: %s", d, e)
|
||||||
platform_utils.rmdir(d)
|
raise DeleteWorktreeError(aggregate_errors=[e])
|
||||||
except OSError as e:
|
elif not platform_utils.listdir(d):
|
||||||
if e.errno != errno.ENOENT:
|
try:
|
||||||
logger.warning("%s: Failed to remove: %s", d, e)
|
platform_utils.rmdir(d)
|
||||||
failed = True
|
except OSError as e:
|
||||||
errors.append(e)
|
if e.errno != errno.ENOENT:
|
||||||
if failed:
|
logger.error("%s: Failed to remove: %s", d, e)
|
||||||
rename_path = (
|
raise DeleteWorktreeError(aggregate_errors=[e])
|
||||||
f"{self.worktree}_repo_to_be_deleted_{int(time.time())}"
|
|
||||||
)
|
except GitError as e:
|
||||||
try:
|
logger.error("Failed to get tracked files with git ls-files: %s", e)
|
||||||
platform_utils.rename(self.worktree, rename_path)
|
raise DeleteWorktreeError(aggregate_errors=[e])
|
||||||
logger.warning(
|
|
||||||
"warning: renamed %s to %s. You can delete it, but you "
|
|
||||||
"might need elevated permissions (e.g. root)",
|
|
||||||
self.worktree,
|
|
||||||
rename_path,
|
|
||||||
)
|
|
||||||
# Rename successful! Clear the errors.
|
|
||||||
errors = []
|
|
||||||
except OSError:
|
|
||||||
logger.error(
|
|
||||||
"%s: Failed to delete obsolete checkout.\n",
|
|
||||||
" Remove manually, then run `repo sync -l`.",
|
|
||||||
self.RelPath(local=False),
|
|
||||||
)
|
|
||||||
raise DeleteWorktreeError(aggregate_errors=errors)
|
|
||||||
|
|
||||||
# Try deleting parent dirs if they are empty.
|
# Try deleting parent dirs if they are empty.
|
||||||
path = self.worktree
|
path = self.worktree
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue