Skip to content

Commit aaea306

Browse files
Attempt to fix exec bit misses (#331)
Fixes #325
1 parent 45ef7ae commit aaea306

5 files changed

Lines changed: 90 additions & 4 deletions

File tree

MODULE.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module(
22
name = "bazel-diff",
3-
version = "18.0.5",
3+
version = "18.1.0",
44
compatibility_level = 0,
55
)
66

MODULE.bazel.lock

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ First, add the following snippet to your project:
320320
#### Bzlmod snippet
321321

322322
```bazel
323-
bazel_dep(name = "bazel-diff", version = "18.0.5")
323+
bazel_dep(name = "bazel-diff", version = "18.1.0")
324324
```
325325

326326
You can now run the tool with:
@@ -447,7 +447,7 @@ bazel run @bazel-diff//cli:bazel-diff -- bazel-diff -h
447447
</tr>
448448
<tr>
449449
<td align="center"><a href="https://github.com/purkhusid"><img src="https://avatars.githubusercontent.com/u/5622403?s=64" width="64" alt="Daniel P. Purkhus"/><br/><sub><b>Daniel P. Purkhus</b></sub></a></td>
450-
<td align="center"><sub><b>Alex Eagle</b></sub></td>
450+
<td align="center"><a href="https://github.com/alexeagle"><img src="https://avatars.githubusercontent.com/u/47395?s=64" width="64" alt="Alex Eagle"/><br/><sub><b>Alex Eagle</b></sub></a></td>
451451
<td align="center"><a href="https://github.com/Malinskiy"><img src="https://avatars.githubusercontent.com/u/2089114?s=64" width="64" alt="Anton Malinskiy"/><br/><sub><b>Anton Malinskiy</b></sub></a></td>
452452
<td align="center"><a href="https://github.com/sharmila-oai"><img src="https://avatars.githubusercontent.com/u/257629015?s=64" width="64" alt="Sharmila"/><br/><sub><b>Sharmila</b></sub></a></td>
453453
<td align="center"><a href="https://github.com/dkostyrev"><img src="https://avatars.githubusercontent.com/u/183590?s=64" width="64" alt="Dmitrii Kostyrev"/><br/><sub><b>Dmitrii Kostyrev</b></sub></a></td>

cli/src/main/kotlin/com/bazel_diff/hash/SourceFileHasher.kt

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package com.bazel_diff.hash
33
import com.bazel_diff.bazel.BazelSourceFileTarget
44
import com.bazel_diff.io.ContentHashProvider
55
import com.bazel_diff.log.Logger
6+
import java.nio.file.Files
67
import java.nio.file.Path
78
import java.nio.file.Paths
9+
import java.nio.file.attribute.PosixFilePermission
810
import org.koin.core.component.KoinComponent
911
import org.koin.core.component.inject
1012
import org.koin.core.qualifier.named
@@ -88,8 +90,19 @@ class SourceFileHasherImpl : KoinComponent, SourceFileHasher {
8890
if (relativeFilenameToContentHash?.contains(filenamePathString) == true) {
8991
val contentHash = relativeFilenameToContentHash.getValue(filenamePathString)
9092
safePutBytes(contentHash.toByteArray())
91-
// Mark that file exists (via content hash)
9293
putBytes(byteArrayOf(0x01))
94+
val absoluteFilePath = workingDirectory.resolve(filenamePath)
95+
val isExecutable =
96+
try {
97+
Files.getPosixFilePermissions(absoluteFilePath).let { perms ->
98+
perms.contains(PosixFilePermission.OWNER_EXECUTE) ||
99+
perms.contains(PosixFilePermission.GROUP_EXECUTE) ||
100+
perms.contains(PosixFilePermission.OTHERS_EXECUTE)
101+
}
102+
} catch (_: Exception) {
103+
absoluteFilePath.toFile().canExecute()
104+
}
105+
putBytes(byteArrayOf(if (isExecutable) 0x01 else 0x00))
93106
} else {
94107
val absoluteFilePath = workingDirectory.resolve(filenamePath)
95108
val file = absoluteFilePath.toFile()
@@ -102,6 +115,17 @@ class SourceFileHasherImpl : KoinComponent, SourceFileHasher {
102115
}
103116
// Mark that file exists
104117
putBytes(byteArrayOf(0x01))
118+
val isExecutable =
119+
try {
120+
Files.getPosixFilePermissions(absoluteFilePath).let { perms ->
121+
perms.contains(PosixFilePermission.OWNER_EXECUTE) ||
122+
perms.contains(PosixFilePermission.GROUP_EXECUTE) ||
123+
perms.contains(PosixFilePermission.OTHERS_EXECUTE)
124+
}
125+
} catch (_: Exception) {
126+
file.canExecute()
127+
}
128+
putBytes(byteArrayOf(if (isExecutable) 0x01 else 0x00))
105129
}
106130
} else {
107131
logger.w { "File $absoluteFilePath not found" }

cli/src/test/kotlin/com/bazel_diff/hash/SourceFileHasherTest.kt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.bazel_diff.extensions.toHexString
99
import com.bazel_diff.testModule
1010
import java.nio.file.Files
1111
import java.nio.file.Paths
12+
import java.nio.file.attribute.PosixFilePermission
1213
import kotlinx.coroutines.runBlocking
1314
import org.junit.Rule
1415
import org.junit.Test
@@ -40,6 +41,7 @@ internal class SourceFileHasherTest : KoinTest {
4041
sha256 {
4142
safePutBytes(fixtureFileContent)
4243
putBytes(byteArrayOf(0x01))
44+
putBytes(byteArrayOf(0x00)) // foo.ts is not executable
4345
safePutBytes(seed)
4446
safePutBytes(fixtureFileTarget.toByteArray())
4547
}
@@ -56,6 +58,7 @@ internal class SourceFileHasherTest : KoinTest {
5658
sha256 {
5759
safePutBytes(fixtureFileContent)
5860
putBytes(byteArrayOf(0x01))
61+
putBytes(byteArrayOf(0x00)) // foo.ts is not executable
5962
safePutBytes(seed)
6063
safePutBytes(fixtureFileTarget.toByteArray())
6164
}
@@ -72,6 +75,7 @@ internal class SourceFileHasherTest : KoinTest {
7275
val expected =
7376
sha256 {
7477
putBytes(byteArrayOf(0x01))
78+
putBytes(byteArrayOf(0x00)) // foo.ts is not executable
7579
safePutBytes(seed)
7680
safePutBytes(fixtureFileTarget.toByteArray())
7781
}
@@ -94,6 +98,7 @@ internal class SourceFileHasherTest : KoinTest {
9498
sha256 {
9599
safePutBytes(externalRepoFileContent.toByteArray())
96100
putBytes(byteArrayOf(0x01))
101+
putBytes(byteArrayOf(0x00)) // writeText creates a non-executable file
97102
safePutBytes(seed)
98103
safePutBytes(externalRepoFileTarget.toByteArray())
99104
}
@@ -110,6 +115,7 @@ internal class SourceFileHasherTest : KoinTest {
110115
sha256 {
111116
safePutBytes(fixtureFileContent)
112117
putBytes(byteArrayOf(0x01))
118+
putBytes(byteArrayOf(0x00)) // foo.ts is not executable
113119
safePutBytes(seed)
114120
safePutBytes(fixtureFileTarget.toByteArray())
115121
}
@@ -171,6 +177,7 @@ internal class SourceFileHasherTest : KoinTest {
171177
sha256 {
172178
safePutBytes("foo-content-hash".toByteArray())
173179
putBytes(byteArrayOf(0x01))
180+
putBytes(byteArrayOf(0x00)) // foo.ts is not executable
174181
safePutBytes(seed)
175182
safePutBytes(fixtureFileTarget.toByteArray())
176183
}
@@ -189,6 +196,7 @@ internal class SourceFileHasherTest : KoinTest {
189196
sha256 {
190197
safePutBytes(fixtureFileContent)
191198
putBytes(byteArrayOf(0x01))
199+
putBytes(byteArrayOf(0x00)) // foo.ts is not executable
192200
safePutBytes(seed)
193201
safePutBytes(fixtureFileTarget.toByteArray())
194202
}
@@ -208,13 +216,42 @@ internal class SourceFileHasherTest : KoinTest {
208216
sha256 {
209217
safePutBytes("foo-content-hash".toByteArray())
210218
putBytes(byteArrayOf(0x01))
219+
putBytes(byteArrayOf(0x00)) // bar.ts does not exist, canExecute() returns false
211220
safePutBytes(seed)
212221
safePutBytes(targetName.toByteArray())
213222
}
214223
.toHexString()
215224
assertThat(actual).isEqualTo(expected)
216225
}
217226

227+
// Reproducer for https://github.com/Tinder/bazel-diff/issues/325
228+
@Test
229+
fun testHashExecutableBitChange() =
230+
runBlocking<Unit> {
231+
val testDir = Files.createTempDirectory("executable_bit_test")
232+
val filePath = testDir.resolve("path/to/script.sh")
233+
Files.createDirectories(filePath.parent)
234+
Files.createFile(filePath)
235+
filePath.toFile().writeText("#!/bin/bash\necho hello")
236+
237+
val target = "//path/to:script.sh"
238+
val hasher = SourceFileHasherImpl(testDir, null, externalRepoResolver)
239+
240+
val permsWithoutExec =
241+
Files.getPosixFilePermissions(filePath) - PosixFilePermission.OWNER_EXECUTE
242+
Files.setPosixFilePermissions(filePath, permsWithoutExec)
243+
val nonExecutableHash =
244+
hasher.digest(BazelSourceFileTarget(target, seed)).toHexString()
245+
246+
val permsWithExec = permsWithoutExec + PosixFilePermission.OWNER_EXECUTE
247+
Files.setPosixFilePermissions(filePath, permsWithExec)
248+
val executableHash =
249+
hasher.digest(BazelSourceFileTarget(target, seed)).toHexString()
250+
251+
// Hashes must differ when executable bit changes — currently fails (issue #325)
252+
assertThat(nonExecutableHash).isNotEqualTo(executableHash)
253+
}
254+
218255
@Test
219256
fun testHashEmptyFileVsDeletedFile() =
220257
runBlocking<Unit> {

0 commit comments

Comments
 (0)