Skip to content

Commit 3fce594

Browse files
committed
Trash: Clean up discarded symlinks properly
1 parent 64a6c66 commit 3fce594

3 files changed

Lines changed: 39 additions & 10 deletions

File tree

gitfourchette/pycompat.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,27 @@ def _pathWalk(self: _Path, top_down=True, on_error=None, follow_symlinks=False):
5555
# Python 3.10, 3.11 compatibility
5656
# (`follow_symlinks` argument in Path.exists() is new in Python 3.12)
5757
if _sys.version_info < (3, 12):
58-
def _pathExists(self: _Path, follow_symlinks=True):
59-
return _pathExistsVanilla(self) if follow_symlinks else _os.path.lexists(self)
58+
def _pathExists(self: _Path, follow_symlinks=True) -> bool:
59+
if follow_symlinks:
60+
return _pathExistsVanilla(self)
61+
return _os.path.lexists(self)
6062

6163
_pathExistsVanilla = _Path.exists
6264
_Path.exists = _pathExists
6365

6466

67+
# Python 3.10, 3.11, 3.12 compatibility
68+
# (`follow_symlinks` argument in Path.is_file() is new in Python 3.13)
69+
if _sys.version_info < (3, 13):
70+
def _pathIsFile(self: _Path, follow_symlinks=True) -> bool:
71+
if follow_symlinks:
72+
return _pathIsFileVanilla(self)
73+
return not self.is_symlink() and _pathIsFileVanilla(self)
74+
75+
_pathIsFileVanilla = _Path.is_file
76+
_Path.is_file = _pathIsFile
77+
78+
6579
# Python 3.10, 3.11, 3.12 compatibility (glob.translate is new Python 3.13)
6680
# Adapted from cpython/Lib/glob.py
6781
if not hasattr(_glob, "translate"):

gitfourchette/trash.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ def exists(self) -> bool:
7878
return self.trashDir.is_dir()
7979

8080
def pathIsTrashManaged(self, p: Path) -> bool:
81-
return p.is_relative_to(self.trashDir) and (p.is_file() or p.is_symlink())
81+
if not p.is_relative_to(self.trashDir):
82+
return False
83+
return p.is_file(follow_symlinks=False) or p.is_symlink()
8284

8385
def refreshFiles(self):
8486
self.trashFiles.clear()
@@ -89,7 +91,7 @@ def refreshFiles(self):
8991
def makeRoom(self, maxFiles: int):
9092
while len(self.trashFiles) > maxFiles:
9193
f = self.trashFiles.pop()
92-
if f.is_file():
94+
if self.pathIsTrashManaged(f):
9395
logger.debug(f"Deleting trash file {f}")
9496
f.unlink()
9597

test/test_trash.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,17 @@ def _fillTrashWithJunk(n):
1515
trash = Trash.instance()
1616
trash.refreshFiles()
1717
trash.clear()
18-
os.makedirs(trash.trashDir, exist_ok=True)
18+
trash.trashDir.mkdir(parents=True, exist_ok=True)
19+
1920
for i in range(n):
20-
with open(F"{trash.trashDir}/19991231T235900-test{i}.txt", "w") as junk:
21-
junk.write(F"test{i}")
21+
junkPath = Path(trash.trashDir, f"19991231T235900-test{i}.txt")
22+
23+
# Alternate between bogus text files and bogus symlinks
24+
if i % 2 == 0:
25+
junkPath.write_text(f"test{i}")
26+
else:
27+
junkPath.symlink_to(trash.trashDir)
28+
2229
trash.refreshFiles()
2330

2431

@@ -109,20 +116,26 @@ def testTrashFull(tempDir, mainWindow):
109116
# Trash should have been purged to make room for new patch
110117
trashInstance = Trash.instance()
111118
assert len(trashInstance.trashFiles) == settings.prefs.maxTrashFiles
119+
assert len(list(trashInstance.trashDir.iterdir())) == settings.prefs.maxTrashFiles
112120
assert "a1.txt" in trashInstance.trashFiles[0].name
113121

114122

115123
def testClearTrash(mainWindow):
116-
assert Trash.instance().count() == 0
124+
trashInstance = Trash.instance()
125+
assert trashInstance.count() == 0
117126

118127
mainWindow.clearRescueFolder()
119128
acceptQMessageBox(mainWindow, "no discarded (patches|changes) to delete")
120129

121130
_fillTrashWithJunk(40)
122-
assert Trash.instance().count() == 40
131+
assert trashInstance.count() == 40
123132
mainWindow.clearRescueFolder()
124133
acceptQMessageBox(mainWindow, "delete.+40.+discarded (patches|changes)")
125-
assert Trash.instance().count() == 0
134+
assert trashInstance.count() == 0
135+
136+
trashInstance.refreshFiles()
137+
assert trashInstance.count() == 0
138+
assert not list(trashInstance.trashDir.iterdir())
126139

127140

128141
def testOpenTrashFolder(mainWindow):

0 commit comments

Comments
 (0)