@@ -13,11 +13,15 @@ import org.eclipse.jgit.util.FS
13
13
import org.gradle.api.DefaultTask
14
14
import org.gradle.api.file.DirectoryProperty
15
15
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.*
17
19
import org.gradle.api.tasks.CacheableTask
18
20
import org.gradle.api.tasks.Input
19
21
import org.gradle.api.tasks.OutputDirectory
20
22
import org.gradle.api.tasks.TaskAction
23
+ import org.gradle.kotlin.dsl.assign
24
+ import org.gradle.kotlin.dsl.of
21
25
import java.io.File
22
26
import javax.inject.Inject
23
27
@@ -26,7 +30,8 @@ import javax.inject.Inject
26
30
*/
27
31
@CacheableTask
28
32
abstract class GitCheckoutTask @Inject constructor(
29
- private val fs : FileSystemOperations
33
+ private val fs : FileSystemOperations ,
34
+ private val providers : ProviderFactory ,
30
35
) : DefaultTask() {
31
36
32
37
@get:OutputDirectory
@@ -40,16 +45,20 @@ abstract class GitCheckoutTask @Inject constructor(
40
45
@get:Input
41
46
abstract val commitId: Property <String >
42
47
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
+
43
59
private val localRepoDir: File
44
60
get() = temporaryDir.resolve(" repo" )
45
61
46
- private val gitOperationsPrinter: ProgressMonitor =
47
- if (logger.isInfoEnabled) {
48
- TextProgressMonitor ()
49
- } else {
50
- NullProgressMonitor .INSTANCE
51
- }
52
-
53
62
init {
54
63
group = " git checkout"
55
64
}
@@ -67,7 +76,7 @@ abstract class GitCheckoutTask @Inject constructor(
67
76
exclude(" .git/" )
68
77
}
69
78
70
- logger.lifecycle(" initialized git repo ${uri.get()} in ${destination.asFile.get()} " )
79
+ logger.lifecycle(" [ $path ] Initialized project ${uri.get()} in ${destination.asFile.get()} " )
71
80
}
72
81
73
82
/* *
@@ -82,17 +91,16 @@ abstract class GitCheckoutTask @Inject constructor(
82
91
val commitId = commitId.get()
83
92
84
93
// 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 " )
89
96
Git .open(localRepoDir)
90
97
} 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
92
100
fs.delete { delete(localRepoDir) }
93
101
94
102
Git .cloneRepository()
95
- .setProgressMonitor(gitOperationsPrinter )
103
+ .setProgressMonitor(logger.asProgressMonitor() )
96
104
.setNoCheckout(true )
97
105
.setURI(uri)
98
106
.setDirectory(localRepoDir)
@@ -102,14 +110,14 @@ abstract class GitCheckoutTask @Inject constructor(
102
110
repo.use { git ->
103
111
// checkout the specific commitId specified in the task input
104
112
git.checkout()
105
- .setProgressMonitor(gitOperationsPrinter )
113
+ .setProgressMonitor(logger.asProgressMonitor() )
106
114
.setForced(true )
107
115
.setName(commitId)
108
116
.call()
109
117
110
118
// git reset --hard (wipe changes to tracked files, if any)
111
119
git.reset()
112
- .setProgressMonitor(gitOperationsPrinter )
120
+ .setProgressMonitor(logger.asProgressMonitor() )
113
121
.setMode(HARD )
114
122
.call()
115
123
@@ -121,4 +129,76 @@ abstract class GitCheckoutTask @Inject constructor(
121
129
.call()
122
130
}
123
131
}
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
+ }
124
204
}
0 commit comments