Skip to content

Commit 8f386ba

Browse files
authored
Update GitCheckoutTask, so it re-runs if the repo is dirty (#3712)
Update GitCheckoutTask, so it re-runs if the repo is dirty Currently, GitCheckoutTask might not re-run if the checked-out repository is dirty. This can cause invalid test results. So, update GitCheckoutTask to re-run if the repo does not exist, is invalid, or contains any modifications. - return a result with a more detailed message - add more logging - when checking if a repo is valid, use the `.git` directory - set `parameters.expectedCommitId = commitId`
1 parent a7fe49e commit 8f386ba

File tree

1 file changed

+98
-18
lines changed

1 file changed

+98
-18
lines changed

build-logic/src/main/kotlin/dokkabuild/tasks/GitCheckoutTask.kt

+98-18
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,15 @@ import org.eclipse.jgit.util.FS
1313
import org.gradle.api.DefaultTask
1414
import org.gradle.api.file.DirectoryProperty
1515
import org.gradle.api.file.FileSystemOperations
16-
import org.gradle.api.provider.Property
16+
import org.gradle.api.logging.Logger
17+
import org.gradle.api.logging.Logging
18+
import org.gradle.api.provider.*
1719
import org.gradle.api.tasks.CacheableTask
1820
import org.gradle.api.tasks.Input
1921
import org.gradle.api.tasks.OutputDirectory
2022
import org.gradle.api.tasks.TaskAction
23+
import org.gradle.kotlin.dsl.assign
24+
import org.gradle.kotlin.dsl.of
2125
import java.io.File
2226
import javax.inject.Inject
2327

@@ -26,7 +30,8 @@ import javax.inject.Inject
2630
*/
2731
@CacheableTask
2832
abstract class GitCheckoutTask @Inject constructor(
29-
private val fs: FileSystemOperations
33+
private val fs: FileSystemOperations,
34+
private val providers: ProviderFactory,
3035
) : DefaultTask() {
3136

3237
@get:OutputDirectory
@@ -40,16 +45,20 @@ abstract class GitCheckoutTask @Inject constructor(
4045
@get:Input
4146
abstract val commitId: Property<String>
4247

48+
init {
49+
outputs.upToDateWhen { task ->
50+
require(task is GitCheckoutTask)
51+
val result = task.gitRepoStatus().get()
52+
if (!result.isUpToDate) {
53+
task.logger.lifecycle("[${task.path}] git repo ${task.localRepoDir} is not up to date. ${result.message}")
54+
}
55+
result.isUpToDate
56+
}
57+
}
58+
4359
private val localRepoDir: File
4460
get() = temporaryDir.resolve("repo")
4561

46-
private val gitOperationsPrinter: ProgressMonitor =
47-
if (logger.isInfoEnabled) {
48-
TextProgressMonitor()
49-
} else {
50-
NullProgressMonitor.INSTANCE
51-
}
52-
5362
init {
5463
group = "git checkout"
5564
}
@@ -67,7 +76,7 @@ abstract class GitCheckoutTask @Inject constructor(
6776
exclude(".git/")
6877
}
6978

70-
logger.lifecycle("initialized git repo ${uri.get()} in ${destination.asFile.get()}")
79+
logger.lifecycle("[$path] Initialized project ${uri.get()} in ${destination.asFile.get()}")
7180
}
7281

7382
/**
@@ -82,17 +91,16 @@ abstract class GitCheckoutTask @Inject constructor(
8291
val commitId = commitId.get()
8392

8493
// Check if the repo is already cloned. If yes, then we can re-use it to save time.
85-
val gitRepoInitialized = RepositoryCache.FileKey.isGitRepository(localRepoDir, FS.DETECTED)
86-
87-
val repo = if (gitRepoInitialized) {
88-
// re-use existing cloned repo
94+
val repo = if (dirContainsGitRepo(localRepoDir)) {
95+
logger.info("[$path] re-using existing cloned repo $localRepoDir")
8996
Git.open(localRepoDir)
9097
} else {
91-
// repo is either not cloned or is not recognizable, so delete it and make a fresh clone
98+
logger.info("[$path] repo $localRepoDir is either not cloned or is not recognizable")
99+
// delete the invalid repo and make a fresh clone
92100
fs.delete { delete(localRepoDir) }
93101

94102
Git.cloneRepository()
95-
.setProgressMonitor(gitOperationsPrinter)
103+
.setProgressMonitor(logger.asProgressMonitor())
96104
.setNoCheckout(true)
97105
.setURI(uri)
98106
.setDirectory(localRepoDir)
@@ -102,14 +110,14 @@ abstract class GitCheckoutTask @Inject constructor(
102110
repo.use { git ->
103111
// checkout the specific commitId specified in the task input
104112
git.checkout()
105-
.setProgressMonitor(gitOperationsPrinter)
113+
.setProgressMonitor(logger.asProgressMonitor())
106114
.setForced(true)
107115
.setName(commitId)
108116
.call()
109117

110118
// git reset --hard (wipe changes to tracked files, if any)
111119
git.reset()
112-
.setProgressMonitor(gitOperationsPrinter)
120+
.setProgressMonitor(logger.asProgressMonitor())
113121
.setMode(HARD)
114122
.call()
115123

@@ -121,4 +129,76 @@ abstract class GitCheckoutTask @Inject constructor(
121129
.call()
122130
}
123131
}
132+
133+
private fun gitRepoStatus(): Provider<GitRepoStatusCheck.Result> =
134+
providers.of(GitRepoStatusCheck::class) {
135+
parameters.repoDir = localRepoDir
136+
parameters.expectedCommitId = commitId
137+
}
138+
139+
/**
140+
* Determine if [repoDir][GitRepoStatusCheck.Params.repoDir] is a valid Git repo,
141+
* and it does not contain any changes.
142+
*/
143+
internal abstract class GitRepoStatusCheck : ValueSource<GitRepoStatusCheck.Result, GitRepoStatusCheck.Params> {
144+
145+
data class Result(
146+
val isUpToDate: Boolean,
147+
val message: String,
148+
)
149+
150+
interface Params : ValueSourceParameters {
151+
val repoDir: DirectoryProperty
152+
val expectedCommitId: Property<String>
153+
}
154+
155+
private val repoDir: File get() = parameters.repoDir.get().asFile
156+
private val expectedCommitId: String get() = parameters.expectedCommitId.get()
157+
158+
private val logger = Logging.getLogger(GitRepoStatusCheck::class.java)
159+
160+
override fun obtain(): Result {
161+
if (!dirContainsGitRepo(repoDir)) {
162+
return Result(
163+
isUpToDate = false,
164+
message = "Repo is either not cloned or is not recognizable as a git repo."
165+
)
166+
}
167+
168+
// Open repository and get the current commit hash
169+
Git.open(repoDir).use { git ->
170+
val currentCommitId = git.repository.findRef("HEAD")?.objectId?.name()
171+
if (currentCommitId != expectedCommitId) {
172+
return Result(
173+
isUpToDate = false,
174+
message = "Repo is not up-to-date. Expected commit-id $expectedCommitId, but was $currentCommitId."
175+
)
176+
}
177+
178+
val status = git.status()
179+
.setProgressMonitor(logger.asProgressMonitor())
180+
.call()
181+
if (!status.isClean) {
182+
return Result(
183+
isUpToDate = false,
184+
message = "Repo is not up-to-date. Found ${status.uncommittedChanges.size} uncommited, ${status.untracked.size} untracked changes."
185+
)
186+
}
187+
return Result(isUpToDate = true, message = "Repo is valid and up-to-date.")
188+
}
189+
}
190+
}
191+
192+
companion object {
193+
194+
private fun dirContainsGitRepo(repoDir: File): Boolean =
195+
RepositoryCache.FileKey.isGitRepository(repoDir.resolve(".git"), FS.DETECTED)
196+
197+
private fun Logger.asProgressMonitor(): ProgressMonitor =
198+
if (isInfoEnabled) {
199+
TextProgressMonitor()
200+
} else {
201+
NullProgressMonitor.INSTANCE
202+
}
203+
}
124204
}

0 commit comments

Comments
 (0)