Skip to content

Commit 13f58f2

Browse files
committed
Implemented pulling implementation for Git LFS
1 parent fe658b4 commit 13f58f2

File tree

11 files changed

+641
-93
lines changed

11 files changed

+641
-93
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.jetpackduba.gitnuro
2+
3+
4+
sealed interface Result<out T, out E> {
5+
data class Ok<T>(val value: T): Result<T, Nothing>
6+
data class Err<E>(val error: E): Result<Nothing, E>
7+
}

src/main/kotlin/com/jetpackduba/gitnuro/git/remote_operations/PullBranchUseCase.kt

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ package com.jetpackduba.gitnuro.git.remote_operations
22

33
import com.jetpackduba.gitnuro.repositories.AppSettingsRepository
44
import kotlinx.coroutines.Dispatchers
5+
import kotlinx.coroutines.delay
56
import kotlinx.coroutines.withContext
67
import org.eclipse.jgit.api.Git
8+
import org.eclipse.jgit.lib.ConfigConstants
9+
import org.eclipse.jgit.lib.Repository
710
import org.eclipse.jgit.transport.CredentialsProvider
811
import javax.inject.Inject
912

@@ -13,25 +16,75 @@ class PullBranchUseCase @Inject constructor(
1316
private val hasPullResultConflictsUseCase: HasPullResultConflictsUseCase,
1417
) {
1518
suspend operator fun invoke(git: Git, pullType: PullType): PullHasConflicts = withContext(Dispatchers.IO) {
16-
val pullWithRebase = when (pullType) {
17-
PullType.REBASE -> true
18-
PullType.MERGE -> false
19-
PullType.DEFAULT -> appSettingsRepository.pullRebase
20-
}
19+
useBuiltinLfs(git.repository) {
20+
val pullWithRebase = when (pullType) {
21+
PullType.REBASE -> true
22+
PullType.MERGE -> false
23+
PullType.DEFAULT -> appSettingsRepository.pullRebase
24+
}
2125

22-
handleTransportUseCase(git) {
23-
val pullResult = git
24-
.pull()
25-
.setTransportConfigCallback { this.handleTransport(it) }
26-
.setRebase(pullWithRebase)
27-
.setCredentialsProvider(CredentialsProvider.getDefault())
28-
.call()
26+
handleTransportUseCase(git) {
27+
val pullResult = git
28+
.pull()
29+
.setTransportConfigCallback { this.handleTransport(it) }
30+
.setRebase(pullWithRebase)
31+
.setCredentialsProvider(CredentialsProvider.getDefault())
32+
.call()
2933

30-
return@handleTransportUseCase hasPullResultConflictsUseCase(pullWithRebase, pullResult)
34+
return@handleTransportUseCase hasPullResultConflictsUseCase(pullWithRebase, pullResult)
35+
}
3136
}
3237
}
3338
}
3439

40+
inline fun <R> useBuiltinLfs(
41+
repository: Repository,
42+
callback: () -> R,
43+
): R {
44+
val lfsSubsection = "lfs"
45+
46+
val names = repository.config.getNames(
47+
ConfigConstants.CONFIG_FILTER_SECTION,
48+
lfsSubsection,
49+
)
50+
51+
// Check if it was set before (if using egit) to restore its value later
52+
val hadBuiltinLfsOriginalValueSet = names.contains(ConfigConstants.CONFIG_KEY_USEJGITBUILTIN)
53+
54+
val builtinLfsOriginalValue = repository.config.getBoolean(
55+
ConfigConstants.CONFIG_FILTER_SECTION,
56+
lfsSubsection,
57+
ConfigConstants.CONFIG_KEY_USEJGITBUILTIN,
58+
false,
59+
)
60+
61+
repository.config.setBoolean(
62+
ConfigConstants.CONFIG_FILTER_SECTION,
63+
lfsSubsection,
64+
ConfigConstants.CONFIG_KEY_USEJGITBUILTIN,
65+
true,
66+
)
67+
68+
val result = callback()
69+
70+
if (hadBuiltinLfsOriginalValueSet) {
71+
repository.config.setBoolean(
72+
ConfigConstants.CONFIG_FILTER_SECTION,
73+
lfsSubsection,
74+
ConfigConstants.CONFIG_KEY_USEJGITBUILTIN,
75+
builtinLfsOriginalValue,
76+
)
77+
} else {
78+
repository.config.unset(
79+
ConfigConstants.CONFIG_FILTER_SECTION,
80+
lfsSubsection,
81+
ConfigConstants.CONFIG_KEY_USEJGITBUILTIN,
82+
)
83+
}
84+
85+
return result
86+
}
87+
3588

3689
enum class PullType {
3790
REBASE,

src/main/kotlin/com/jetpackduba/gitnuro/git/remote_operations/PushBranchUseCase.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import org.eclipse.jgit.lib.Constants
1414
import org.eclipse.jgit.lib.ProgressMonitor
1515
import org.eclipse.jgit.transport.RefLeaseSpec
1616
import org.eclipse.jgit.transport.RefSpec
17+
import java.io.PrintStream
1718
import javax.inject.Inject
1819
import kotlin.math.max
1920

@@ -102,8 +103,12 @@ class PushBranchUseCase @Inject constructor(
102103
override fun isCancelled() = !isActive
103104
override fun showDuration(enabled: Boolean) {}
104105
})
106+
.setHookOutputStream(System.out)
107+
.setHookErrorStream(System.err)
105108
.call()
106109

110+
111+
107112
val results = pushResult
108113
.map {
109114
it.remoteUpdates.filter { remoteRefUpdate -> remoteRefUpdate.status.isRejected }

src/main/kotlin/com/jetpackduba/gitnuro/lfs/GLfsFactory.kt

Lines changed: 106 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package com.jetpackduba.gitnuro.lfs
22

3+
import com.jetpackduba.gitnuro.Result
34
import com.jetpackduba.gitnuro.credentials.CredentialsAccepted
45
import com.jetpackduba.gitnuro.credentials.CredentialsRequest
56
import com.jetpackduba.gitnuro.credentials.CredentialsStateManager
7+
import com.jetpackduba.gitnuro.logging.printLog
68
import com.jetpackduba.gitnuro.models.lfs.LfsObjectBatch
9+
import com.jetpackduba.gitnuro.models.lfs.LfsObjects
710
import com.jetpackduba.gitnuro.models.lfs.LfsPrepareUploadObjectBatch
8-
import com.jetpackduba.gitnuro.models.lfs.LfsRef
911
import io.ktor.client.*
1012
import io.ktor.client.engine.cio.*
1113
import io.ktor.client.plugins.contentnegotiation.*
@@ -15,13 +17,9 @@ import io.ktor.http.*
1517
import io.ktor.serialization.kotlinx.json.*
1618
import io.ktor.util.cio.*
1719
import kotlinx.coroutines.runBlocking
18-
import kotlinx.serialization.encodeToString
19-
import kotlinx.serialization.json.Json
2020
import org.eclipse.jgit.annotations.Nullable
21-
import org.eclipse.jgit.api.Git
2221
import org.eclipse.jgit.attributes.Attribute
23-
import org.eclipse.jgit.errors.IncorrectObjectTypeException
24-
import org.eclipse.jgit.errors.MissingObjectException
22+
import org.eclipse.jgit.attributes.FilterCommandRegistry
2523
import org.eclipse.jgit.hooks.PrePushHook
2624
import org.eclipse.jgit.lfs.*
2725
import org.eclipse.jgit.lib.*
@@ -32,24 +30,33 @@ import org.eclipse.jgit.util.LfsFactory
3230
import java.io.IOException
3331
import java.io.InputStream
3432
import java.io.PrintStream
35-
import java.security.cert.X509Certificate
3633
import java.util.*
3734
import java.util.concurrent.CancellationException
3835
import javax.inject.Inject
3936
import javax.inject.Singleton
40-
import javax.net.ssl.X509TrustManager
41-
import javax.swing.text.AbstractDocument.Content
4237

38+
private const val TAG = "GLfsFactory"
4339

4440
@Singleton
4541
class GLfsFactory @Inject constructor(
4642
private val lfsRepository: LfsRepository,
4743
private val credentialsStateManager: CredentialsStateManager,
4844
) : LfsFactory() {
45+
init {
46+
FilterCommandRegistry.register("jgit://builtin/lfs/smudge") { repository, input, out ->
47+
LfsSmudgeFilter(lfsRepository, credentialsStateManager, input, out, repository)
48+
}
49+
FilterCommandRegistry.register("jgit://builtin/lfs/clean") { db, `in`, out ->
50+
LfsCleanFilter(db, `in`, out)
51+
}
52+
}
53+
54+
4955
fun register() {
5056
setInstance(this)
5157
}
5258

59+
5360
override fun isAvailable(): Boolean {
5461
return true
5562
}
@@ -65,7 +72,7 @@ class GLfsFactory @Inject constructor(
6572

6673
@Throws(IOException::class)
6774
override fun applyCleanFilter(
68-
db: Repository?,
75+
db: Repository,
6976
input: InputStream?,
7077
length: Long,
7178
attribute: Attribute?
@@ -121,6 +128,18 @@ class GLfsFactory @Inject constructor(
121128
}
122129
}
123130

131+
//class GSmudgeFilter(
132+
// repository: Repository,
133+
// input: InputStream?,
134+
// output: OutputStream?,
135+
//) : SmudgeFilter(
136+
// repository,
137+
// input,
138+
// output,
139+
//) {
140+
//
141+
//}
142+
124143
class GLfsPrePushHook(
125144
repository: Repository,
126145
outputStream: PrintStream?,
@@ -212,70 +231,101 @@ class GLfsPrePushHook(
212231
return Constants.R_REMOTES + remoteName
213232
}
214233

215-
fun getLfsRepositoryUrl(repository: Repository): String {
216-
// TODO Obtain proper url
217-
return "https://localhost:8080"
218-
}
219-
220234
override fun call(): String = runBlocking {
221235
val toPush = findObjectsToPush()
222236
if (toPush.isEmpty()) {
223237
return@runBlocking ""
224238
}
225239

226-
val uploadBatch = LfsPrepareUploadObjectBatch(
227-
operation = "upload",
228-
objects = toPush.map {
229-
LfsObjectBatch(it.oid.name(), it.size)
230-
},
231-
transfers = listOf(
232-
"lfs-standalone-file",
233-
"basic",
234-
"ssh",
235-
),
236-
ref = LfsRef(repository.fullBranch),
237-
hashAlgo = "sha256",
238-
)
239-
240240
if (!isDryRun) {
241-
val lfsServerUrl = getLfsRepositoryUrl(repository)
242-
val requiresAuth = lfsRepository.requiresAuthForBatchObjects(lfsServerUrl)
241+
val lfsServerUrl = lfsRepository.getLfsRepositoryUrl(repository)
243242

244-
var username: String? = null
245-
var password: String? = null
246-
247-
if (requiresAuth) {
248-
credentialsStateManager.requestCredentials(CredentialsRequest.LfsCredentialsRequest)
243+
val lfsPrepareUploadObjectBatch = createLfsPrepareUploadObjectBatch(
244+
OperationType.UPLOAD,
245+
branch = repository.fullBranch,
246+
objects = toPush.map { LfsObjectBatch(it.oid.name(), it.size) },
247+
)
249248

250-
var credentials = credentialsStateManager.currentCredentialsState
251-
while (credentials is CredentialsRequest) {
252-
credentials = credentialsStateManager.currentCredentialsState
249+
when (val lfsObjects = getLfsObjects(lfsServerUrl, lfsPrepareUploadObjectBatch)) {
250+
is Result.Err -> {
251+
throw Exception("LFS Error ${lfsObjects.error}")
253252
}
254253

255-
if (credentials !is CredentialsAccepted.LfsCredentialsAccepted)
256-
throw CancellationException("Credentials cancelled")
257-
else {
258-
username = credentials.user
259-
password = credentials.password
254+
is Result.Ok -> for (p in toPush) {
255+
val lfs = Lfs(repository)
256+
257+
printLog("LFS", "Requesting credentials for objects upload")
258+
val credentials = requestLfsCredentials()
259+
260+
lfsObjects.value.objects.forEach { obj ->
261+
val uploadUrl = obj.actions.upload?.href
262+
263+
if (uploadUrl != null) {
264+
lfsRepository.uploadObject(
265+
uploadUrl,
266+
p.oid.name(),
267+
lfs.getMediaFile(p.oid),
268+
p.size,
269+
credentials.user,
270+
credentials.password,
271+
)
272+
}
273+
}
260274
}
261275
}
276+
}
262277

263-
lfsRepository.batchObjects(lfsServerUrl, uploadBatch, username, password)
278+
return@runBlocking ""
279+
}
264280

265-
for (p in toPush) {
266-
val lfs = Lfs(repository)
281+
private suspend fun getLfsObjects(
282+
lfsServerUrl: String,
283+
lfsPrepareUploadObjectBatch: LfsPrepareUploadObjectBatch,
284+
): Result<LfsObjects, LfsError> {
267285

268-
lfsRepository.uploadObject(
269-
lfsServerUrl,
270-
p.oid.name(),
271-
lfs.getMediaFile(p.oid),
272-
p.size,
273-
username,
274-
password,
275-
)
286+
var lfsObjects: Result<LfsObjects, LfsError>
287+
var requiresAuth: Boolean
288+
289+
var username: String? = null
290+
var password: String? = null
291+
292+
293+
do {
294+
val newLfsObjects = lfsRepository.postBatchObjects(
295+
lfsServerUrl,
296+
lfsPrepareUploadObjectBatch,
297+
username,
298+
password,
299+
)
300+
301+
requiresAuth = newLfsObjects is Result.Err &&
302+
newLfsObjects.error is LfsError.HttpError &&
303+
newLfsObjects.error.code == HttpStatusCode.Unauthorized
304+
305+
if (requiresAuth) {
306+
val credentials = requestLfsCredentials()
307+
username = credentials.user
308+
password = credentials.password
276309
}
310+
311+
lfsObjects = newLfsObjects
312+
} while (requiresAuth)
313+
314+
return lfsObjects
315+
}
316+
317+
private fun requestLfsCredentials(): CredentialsAccepted.LfsCredentialsAccepted {
318+
credentialsStateManager.requestCredentials(CredentialsRequest.LfsCredentialsRequest)
319+
320+
var credentials = credentialsStateManager.currentCredentialsState
321+
while (credentials is CredentialsRequest) {
322+
credentials = credentialsStateManager.currentCredentialsState
277323
}
278324

279-
return@runBlocking ""
325+
if (credentials !is CredentialsAccepted.LfsCredentialsAccepted) {
326+
throw CancellationException("Credentials cancelled") // TODO Improve this error
327+
}
328+
329+
return credentials
280330
}
281331
}

0 commit comments

Comments
 (0)