Skip to content

Commit 7054dc4

Browse files
committed
Content: Get rid of GitAPIException "expection"
1 parent 951c6ac commit 7054dc4

14 files changed

+95
-38
lines changed

src/main/groovy/com/cloudogu/gitops/features/Content.groovy

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ import jakarta.inject.Singleton
1717
import org.apache.commons.io.FileUtils
1818
import org.eclipse.jgit.api.CloneCommand
1919
import org.eclipse.jgit.api.Git
20-
import org.eclipse.jgit.api.errors.GitAPIException
20+
import org.eclipse.jgit.api.ResetCommand
21+
import org.eclipse.jgit.lib.Ref
22+
import org.eclipse.jgit.lib.Repository
2123
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider
2224

2325
@Slf4j
@@ -168,25 +170,49 @@ class Content extends Feature {
168170
def cloneCommand = gitClone()
169171
.setURI(repo.url)
170172
.setDirectory(repoTmpDir)
171-
.setCloneAllBranches(true)
172173

173174
if (repo.username != null && repo.password != null) {
174175
cloneCommand.setCredentialsProvider(
175176
new UsernamePasswordCredentialsProvider(repo.username, repo.password))
176177
}
177178
def git = cloneCommand.call()
178-
try {
179-
// switch to used branch.
180-
git.checkout().setName(repo.ref).call()
179+
180+
def actualRef = findRef(repo, git.repository)
181181

182-
} catch (GitAPIException e) {
183-
// This is a fallback because of branches hosted at github.
184-
log.debug("checkout branch ${repo.ref} not working, maybe because of github. Now again with origin/${repo.ref} to checkout remote branch.")
185-
var nameWithOrigin = 'origin/'+ repo.ref
186-
git.checkout().setName(nameWithOrigin).call()
182+
// Avoid jgit removing and staging all files except .git which might lead to CheckoutConflictException during checkout
183+
git.reset().setMode(ResetCommand.ResetType.HARD).call()
184+
git.checkout().setName(actualRef).call()
185+
}
187186

187+
private String findRef(Config.ContentSchema.ContentRepositorySchema repoConfig, Repository gitRepo) {
188+
// Check if it is a commit hash first to avoid InvalidRefNameException
189+
if (gitRepo.resolve(repoConfig.ref)) {
190+
return repoConfig.ref
191+
}
192+
193+
// Check tags or branches
194+
def remoteCommand = Git.lsRemoteRepository()
195+
.setRemote(repoConfig.url)
196+
.setHeads(true)
197+
.setTags(true)
198+
199+
if (repoConfig.username != null && repoConfig.password != null) {
200+
remoteCommand.setCredentialsProvider(
201+
new UsernamePasswordCredentialsProvider(repoConfig.username, repoConfig.password))
188202
}
203+
Collection<Ref> refs = remoteCommand.call()
204+
String potentialRef = refs.find { it.name.endsWith(repoConfig.ref) }?.name
205+
206+
if (!potentialRef) {
207+
// Jgit silently ignores some missing refs and just continues with default branch.
208+
// This might lead to unexpected surprises for our users, so better fail explicitly
209+
throw new RuntimeException("Reference '${repoConfig.ref}' not found in repository '${repoConfig.url}'")
189210
}
211+
212+
// Jgit only checks out remote branches when they start in origin/ 🙄
213+
return potentialRef.replace('refs/heads/', 'origin/')
214+
}
215+
190216

191217
protected void pushTargetRepos(List<RepoCoordinate> repoCoordinates) {
192218
repoCoordinates.each { repoCoordinate ->

src/test/groovy/com/cloudogu/gitops/features/ContentTest.groovy

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.cloudogu.gitops.scmm.api.Permission
66
import com.cloudogu.gitops.scmm.api.Repository
77
import com.cloudogu.gitops.scmm.api.ScmmApiClient
88
import com.cloudogu.gitops.utils.*
9+
import groovy.util.logging.Slf4j
910
import groovy.yaml.YamlSlurper
1011
import org.apache.commons.io.FileUtils
1112
import org.eclipse.jgit.api.CloneCommand
@@ -16,10 +17,12 @@ import org.junit.jupiter.api.AfterAll
1617
import org.junit.jupiter.api.Test
1718
import org.mockito.ArgumentCaptor
1819

20+
import static groovy.test.GroovyAssert.shouldFail
1921
import static org.assertj.core.api.Assertions.assertThat
2022
import static org.mockito.ArgumentMatchers.*
2123
import static org.mockito.Mockito.*
2224

25+
@Slf4j
2326
class ContentTest {
2427
// bareRepo
2528
static List<File> foldersToDelete = new ArrayList<File>()
@@ -191,16 +194,36 @@ class ContentTest {
191194
}
192195

193196
@Test
194-
void 'Checks out commit refs and tags for content repos'() {
197+
void 'Checks out commit refs, tags and non-default branches for content repos'() {
195198
config.content.repos = [
196-
new Config.ContentSchema.ContentRepositorySchema(url: createContentRepo('nonFolderBasedRepo1'), ref: 'someTag', folderBased: false, target: 'common/tag'),
197-
new Config.ContentSchema.ContentRepositorySchema(url: createContentRepo('nonFolderBasedRepo2'), ref: '56d2e3f4b7c95d5645c823f7be8ea6f8a853ac40', folderBased: false, target: 'common/ref')
199+
new Config.ContentSchema.ContentRepositorySchema(url: createContentRepo(), ref: 'someTag', folderBased: false, target: 'common/tag'),
200+
new Config.ContentSchema.ContentRepositorySchema(url: createContentRepo(), ref: '8bc1d1165468359b16d9771d4a9a3df26afc03e8', folderBased: false, target: 'common/ref'),
201+
new Config.ContentSchema.ContentRepositorySchema(url: createContentRepo(), ref: 'someBranch', folderBased: false, target: 'common/branch')
198202
]
199203

200204
def repos = createContent().cloneContentRepos()
201205

202206
assertThat(new File(findRoot(repos), "common/tag/README.md")).exists().isFile()
207+
assertThat(new File(findRoot(repos), "common/tag/README.md").text).contains("someTag")
208+
203209
assertThat(new File(findRoot(repos), "common/ref/README.md")).exists().isFile()
210+
assertThat(new File(findRoot(repos), "common/ref/README.md").text).contains("main")
211+
212+
assertThat(new File(findRoot(repos), "common/branch/README.md")).exists().isFile()
213+
assertThat(new File(findRoot(repos), "common/branch/README.md").text).contains("someBranch")
214+
}
215+
216+
@Test
217+
void 'Fails if commit refs does not exit'() {
218+
config.content.repos = [
219+
new Config.ContentSchema.ContentRepositorySchema(url: createContentRepo(), ref: 'someTag', folderBased: false, target: 'common/tag'),
220+
new Config.ContentSchema.ContentRepositorySchema(url: createContentRepo(), ref: 'does/not/exist', folderBased: true, target: 'does not matter'),
221+
]
222+
223+
def exception = shouldFail(RuntimeException) {
224+
createContent().cloneContentRepos()
225+
}
226+
assertThat(exception.message).startsWith("Reference 'does/not/exist' not found in repository")
204227
}
205228

206229
@Test
@@ -466,36 +489,38 @@ class ContentTest {
466489
}
467490

468491

469-
static String createContentRepo(String srcPath) {
492+
static String createContentRepo(String srcPath = '') {
470493
// The bare repo works as the "remote"
471494
def bareRepoDir = File.createTempDir('gitops-playground-test-content-repo')
472495
bareRepoDir.deleteOnExit()
473496
foldersToDelete << bareRepoDir
474497
// init with bare repo
475498
FileUtils.copyDirectory(new File(System.getProperty("user.dir") + "/src/test/groovy/com/cloudogu/gitops/utils/data/git-repository/"), bareRepoDir)
476499
def bareRepoUri = 'file://' + bareRepoDir.absolutePath
477-
println("Repo $srcPath: bare repo $bareRepoUri")
478-
479-
// Add srcPath to bare repo
480-
def tempRepo = File.createTempDir('gitops-playground-temp-repo')
481-
tempRepo.deleteOnExit()
482-
foldersToDelete << tempRepo
483-
println("Repo $srcPath: cloned bare repo to $tempRepo")
484-
def git = Git.cloneRepository()
485-
.setURI(bareRepoUri)
486-
.setBranch('main')
487-
.setDirectory(tempRepo)
488-
.call()
489-
490-
FileUtils.copyDirectory(new File(System.getProperty("user.dir") + '/src/test/groovy/com/cloudogu/gitops/utils/data/contentRepos/' + srcPath), tempRepo)
491-
492-
git.add().addFilepattern(".").call()
493-
494-
// Avoid complications with local developer's git config, e.g. when git config --global commit.gpgSign true
495-
SystemReader.getInstance().userConfig.clear()
496-
git.commit().setMessage("Initialize with $srcPath").call()
497-
git.push().call()
498-
tempRepo.delete()
500+
log.debug("Repo $srcPath: bare repo $bareRepoUri")
501+
502+
if (srcPath) {
503+
// Add srcPath to bare repo
504+
def tempRepo = File.createTempDir('gitops-playground-temp-repo')
505+
tempRepo.deleteOnExit()
506+
foldersToDelete << tempRepo
507+
log.debug("Repo $srcPath: cloned bare repo to $tempRepo")
508+
def git = Git.cloneRepository()
509+
.setURI(bareRepoUri)
510+
.setBranch('main')
511+
.setDirectory(tempRepo)
512+
.call()
513+
514+
FileUtils.copyDirectory(new File(System.getProperty("user.dir") + '/src/test/groovy/com/cloudogu/gitops/utils/data/contentRepos/' + srcPath), tempRepo)
515+
516+
git.add().addFilepattern(".").call()
517+
518+
// Avoid complications with local developer's git config, e.g. when git config --global commit.gpgSign true
519+
SystemReader.getInstance().userConfig.clear()
520+
git.commit().setMessage("Initialize with $srcPath").call()
521+
git.push().call()
522+
tempRepo.delete()
523+
}
499524

500525
return bareRepoUri
501526
}

src/test/groovy/com/cloudogu/gitops/utils/data/git-repository/objects/15/5e9388fc29687b92a4ae2470458a5e08be9a81

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

src/test/groovy/com/cloudogu/gitops/utils/data/git-repository/objects/8b/c1d1165468359b16d9771d4a9a3df26afc03e8

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

0 commit comments

Comments
 (0)