Skip to content

Commit 90626df

Browse files
counter2015lihaoyi
andauthored
Fix empty folder missing problem when zip files. (#330)
Fix: #328 --------- Co-authored-by: Li Haoyi <[email protected]>
1 parent a3a901c commit 90626df

File tree

8 files changed

+70
-35
lines changed

8 files changed

+70
-35
lines changed

os/src/ZipOps.scala

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ object zip {
6262
excludePatterns,
6363
includePatterns,
6464
(path, sub) => {
65-
os.copy(path, opened / sub, createFolders = true)
65+
os.copy.over(path, opened / sub, createFolders = true)
6666
if (!preserveMtimes) {
6767
os.mtime.set(opened / sub, 0)
6868
// This is the only way we can properly zero out filesystem metadata within the
@@ -98,10 +98,12 @@ object zip {
9898
sources.foreach { source =>
9999
if (os.isDir(source.src)) {
100100
for (path <- os.walk(source.src)) {
101-
if (os.isFile(path) && shouldInclude(path.toString, excludePatterns, includePatterns)) {
102-
makeZipEntry0(path, source.dest.getOrElse(os.sub) / path.subRelativeTo(source.src))
101+
if (shouldInclude(path.toString, excludePatterns, includePatterns)) {
102+
val target = source.dest.getOrElse(os.sub) / path.subRelativeTo(source.src / os.up)
103+
makeZipEntry0(path, target)
103104
}
104105
}
106+
makeZipEntry0(source.src, source.dest.getOrElse(os.sub / source.src.last))
105107
} else if (shouldInclude(source.src.last, excludePatterns, includePatterns)) {
106108
makeZipEntry0(source.src, source.dest.getOrElse(os.sub / source.src.last))
107109
}
@@ -153,6 +155,7 @@ object zip {
153155
val mtimeOpt = if (preserveMtimes) Some(os.mtime(file)) else None
154156

155157
val fis = if (os.isFile(file)) Some(os.read.inputStream(file)) else None
158+
156159
try makeZipEntry0(sub, fis, mtimeOpt, zipOut)
157160
finally fis.foreach(_.close())
158161
}
@@ -163,7 +166,14 @@ object zip {
163166
preserveMtimes: Option[Long],
164167
zipOut: ZipOutputStream
165168
) = {
166-
val zipEntry = new ZipEntry(sub.toString)
169+
val path = is match {
170+
// for folder
171+
case None => sub.toString + "/"
172+
// for file
173+
case Some(_) => sub.toString
174+
}
175+
176+
val zipEntry = new ZipEntry(path)
167177

168178
preserveMtimes match {
169179
case Some(mtime) => zipEntry.setTime(mtime)

os/test/resources/test/emptyFolder/.gitkeep

Whitespace-only changes.

os/test/src-jvm/SpawningSubprocessesNewTests.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ object SpawningSubprocessesNewTests extends TestSuite {
8686
stdout =
8787
os.ProcessOutput((buf, len) => lineCount += buf.slice(0, len).count(_ == '\n'))
8888
)
89-
lineCount ==> 22
89+
lineCount ==> 24
9090
}
9191
}
9292
test - prep { wd =>
@@ -97,7 +97,7 @@ object SpawningSubprocessesNewTests extends TestSuite {
9797
cwd = wd,
9898
stdout = os.ProcessOutput.Readlines(line => lineCount += 1)
9999
)
100-
lineCount ==> 22
100+
lineCount ==> 24
101101
}
102102
}
103103
}

os/test/src-jvm/SpawningSubprocessesTests.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ object SpawningSubprocessesTests extends TestSuite {
8282
stdout =
8383
os.ProcessOutput((buf, len) => lineCount += buf.slice(0, len).count(_ == '\n'))
8484
)
85-
lineCount ==> 22
85+
lineCount ==> 24
8686
}
8787
}
8888
test - prep { wd =>
@@ -92,7 +92,7 @@ object SpawningSubprocessesTests extends TestSuite {
9292
cwd = wd,
9393
stdout = os.ProcessOutput.Readlines(line => lineCount += 1)
9494
)
95-
lineCount ==> 22
95+
lineCount ==> 24
9696
}
9797
}
9898
}

os/test/src-jvm/ZipOpJvmTests.scala

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,21 @@ object ZipOpJvmTests extends TestSuite {
3636
dest = wd / "unzipped folder"
3737
)
3838

39-
val paths = os.walk(unzippedFolder)
40-
val expected = Seq(
39+
val paths = os.walk(unzippedFolder).toList.sorted
40+
val expected = List(
4141
// Files get included in the zip root using their name
4242
wd / "unzipped folder/File.txt",
4343
wd / "unzipped folder/Multi Line.txt",
4444
// Folder contents get included relative to the source root
45-
wd / "unzipped folder/nestedA",
46-
wd / "unzipped folder/nestedB",
47-
wd / "unzipped folder/one.txt",
48-
wd / "unzipped folder/nestedA/a.txt",
49-
wd / "unzipped folder/nestedB/b.txt"
50-
)
51-
assert(paths.sorted == expected)
45+
wd / "unzipped folder/folder1",
46+
wd / "unzipped folder/folder1/one.txt",
47+
wd / "unzipped folder/folder2",
48+
wd / "unzipped folder/folder2/nestedA",
49+
wd / "unzipped folder/folder2/nestedA/a.txt",
50+
wd / "unzipped folder/folder2/nestedB",
51+
wd / "unzipped folder/folder2/nestedB/b.txt"
52+
).sorted
53+
assert(paths == expected)
5254
}
5355

5456
test("zipAndUnzipPreserveMtimes") - prep { wd =>

os/test/src/CheckerTests.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -453,8 +453,9 @@ object CheckerTests extends TestSuite {
453453
val unzipDir = os.unzip(zipFile, wd / "unzipped")
454454
os.walk(unzipDir).sorted ==> Seq(
455455
unzipDir / "File.txt",
456-
unzipDir / "one.txt"
457-
)
456+
unzipDir / "folder1/one.txt",
457+
unzipDir / "folder1"
458+
).sorted
458459
}
459460
test("unzip") - prepChecker { wd =>
460461
val zipFileName = "zipped.zip"
@@ -478,7 +479,7 @@ object CheckerTests extends TestSuite {
478479
source = zipFile,
479480
dest = wd / "unzipped"
480481
)
481-
os.walk(unzipDir).length ==> 2
482+
os.walk(unzipDir).length ==> 3
482483
}
483484
}
484485
}

os/test/src/OpTests.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ object OpTests extends TestSuite {
1818
res / "misc",
1919
res / "os",
2020
res / "File.txt",
21-
res / "Multi Line.txt"
21+
res / "Multi Line.txt",
22+
res / "emptyFolder"
2223
),
2324
os.list(res / "folder2").toSet == Set(
2425
res / "folder2/nestedA",

os/test/src/ZipOpTests.scala

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,10 @@ object ZipOpTests extends TestSuite {
5454
val expected = Seq(
5555
wd / "unzipped folder/renamed-file.txt",
5656
wd / "unzipped folder/renamed-folder",
57-
wd / "unzipped folder/renamed-folder/one.txt"
57+
wd / "unzipped folder/renamed-folder/folder1",
58+
wd / "unzipped folder/renamed-folder/folder1/one.txt"
5859
)
59-
assert(paths.sorted == expected)
60+
assert(paths.sorted == expected.sorted)
6061
}
6162

6263
test("excludePatterns") - prep { wd =>
@@ -80,8 +81,9 @@ object ZipOpTests extends TestSuite {
8081
zipFile1,
8182
dest = wd / "zipByExcludingCertainFiles"
8283
)
83-
val paths = os.walk(outputZipFilePath).sorted
84-
val expected = Seq(wd / "zipByExcludingCertainFiles/File.amx")
84+
val paths = os.walk(outputZipFilePath).toList.sorted
85+
val expected =
86+
Seq(wd / "zipByExcludingCertainFiles/File.amx").toList.sorted
8587
assert(paths == expected)
8688
}
8789

@@ -104,8 +106,8 @@ object ZipOpTests extends TestSuite {
104106
// Unzip file to check for contents
105107
val outputZipFilePath =
106108
os.unzip(zipFile1, dest = wd / "zipByIncludingCertainFiles")
107-
val paths = os.walk(outputZipFilePath)
108-
val expected = Seq(wd / "zipByIncludingCertainFiles" / amxFile)
109+
val paths = os.walk(outputZipFilePath).toSeq.sorted
110+
val expected = List(wd / "zipByIncludingCertainFiles" / amxFile).sorted
109111
assert(paths == expected)
110112
}
111113

@@ -124,8 +126,8 @@ object ZipOpTests extends TestSuite {
124126
dest = wd / "zipStreamFunction"
125127
)
126128

127-
val paths = os.walk(unzippedFolder)
128-
assert(paths == Seq(unzippedFolder / "File.txt"))
129+
val paths = os.walk(unzippedFolder).toSeq
130+
assert(paths.sorted == Seq(unzippedFolder / "File.txt").sorted)
129131
}
130132

131133
test("list") - prep { wd =>
@@ -140,9 +142,10 @@ object ZipOpTests extends TestSuite {
140142
)
141143

142144
// Unzip file to a destination folder
143-
val listedContents = os.unzip.list(source = wd / zipFileName).toSeq
145+
val listedContents = os.unzip.list(source = wd / zipFileName).toList.sorted
144146

145-
val expected = Seq(os.sub / "File.txt", os.sub / "one.txt")
147+
val expected =
148+
List(os.sub / "File.txt", os.sub / "folder1/one.txt", os.sub / "folder1").sorted
146149
assert(listedContents == expected)
147150
}
148151

@@ -167,11 +170,12 @@ object ZipOpTests extends TestSuite {
167170
excludePatterns = Seq(amxFile.r)
168171
)
169172

170-
val paths = os.walk(unzippedFolder)
171-
val expected = Seq(
173+
val paths = os.walk(unzippedFolder).toList.sorted
174+
val expected = List(
172175
wd / "unzipAllExceptExcludingCertainFiles/File.txt",
173-
wd / "unzipAllExceptExcludingCertainFiles/one.txt"
174-
)
176+
wd / "unzipAllExceptExcludingCertainFiles/folder1/one.txt",
177+
wd / "unzipAllExceptExcludingCertainFiles/folder1"
178+
).sorted
175179

176180
assert(paths == expected)
177181
}
@@ -222,5 +226,22 @@ object ZipOpTests extends TestSuite {
222226
assert(file2Content == "Content of file2")
223227
}
224228

229+
test("emptyFolder") - prep { wd =>
230+
val zipFileName = "zipCheckEmptyDirectory.zip"
231+
val zipFile = os.zip(
232+
dest = wd / zipFileName,
233+
sources = Seq(
234+
wd / "emptyFolder",
235+
wd / "File.txt"
236+
)
237+
)
238+
239+
val unzippedFolder = os.unzip(
240+
source = wd / zipFileName,
241+
dest = wd / "unzipped-empty-directory"
242+
)
243+
244+
assert(os.exists(unzippedFolder / "emptyFolder"))
245+
}
225246
}
226247
}

0 commit comments

Comments
 (0)