jenkinsci / jenkinsci/git-plugin

[JENKINS-39968] git checkouts are not pipeline-parallel safe

Open
#2,996 10 comments 0 reactions 0 assignees View on GitHub
component:git-plugin component:workflow-scm-step-plugin imported-jira-issue priority:major resolution:unresolved
Dominant language
Java
Stars
694
Forks
1.1k
Avg merge
1h 29m
Merged PRs (30d)
3

Description

We have a specific workflow that enables us to discover that bug. However, it could and will probably happen to other people soon.

When running scm checkouts in parallels in a Jenkins file, the changes discovered are discovered for every git checkout that runs at the same time.

In our example, we run checkouts with 8 threads in parallel (background: https://roidelapluie.be/blog/2016/11/18/gitslave-jenkins/ ).

One change is displayed in 8 different repositories, the 8 repositories that are checked out at the same time.

The next checkouts do not contains the changes, so I guess it is kind of a global variable that is reset after the checkout.

lock('checkout') {

def checkouts = [:]
def threads = 8

stage('Super Repo') {
checkout([$class: 'GitSCM',
branches: [[name: branch]],
doGenerateSubmoduleConfigurations: false,
extensions: [[$class: 'CleanCheckout'], [$class: 'ScmName', name: 'super']],
submoduleCfg: [],
userRemoteConfigs: [[url: "${gitBaseUrl}/${superRepo}"]]])

def repos = readFile('.gitslave')
def reposLines = repos.readLines()
for (i = 0; i < threads; i++){
checkouts["Thread ${i}"] = []
}
def idx = 0
for (line in reposLines) {
def repoInfo = line.split(' ')
def repoUrl = repoInfo[0]
def repoPath = repoInfo[1]
def curatedRepoUrl = repoUrl.substring(4, repoUrl.length()-1)
def curatedRepoPath = repoPath.substring(1, repoPath.length()-1)
echo("Thread ${idx%threads}")
checkouts["Thread ${idx%threads}"] << [path: curatedRepoPath, url: "${gitBaseUrl}/${curatedRepoUrl}"]
idx++
}
}
stage('GitSlave Repos') {
def doCheckouts = [:]
for (i = 0; i < threads; i++){
def j = i
doCheckouts["Thread ${j}"] = {
for (co in checkouts["Thread ${j}"]) {
retry(3) {
checkout([$class: 'GitSCM',
branches: [[name: branch]],
doGenerateSubmoduleConfigurations: false,
extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: co.path], [$class: 'CleanCheckout'], [$class: 'ScmName', name: co.path]],
submoduleCfg: [],
userRemoteConfigs: [[url: co.url]]])
}
}
}
}
parallel doCheckouts
}

---
Originally reported by roidelapluie, imported from: git checkouts are not pipeline-parallel safe


  • status: Open
  • priority: Major
  • component(s): git-plugin, workflow-scm-step-plugin
  • resolution: Unresolved
  • votes: 5
  • watchers: 10
  • imported: 2025-12-02

Raw content of original issue

We have a specific workflow that enables us to discover that bug. However, it could and will probably happen to other people soon.

When running scm checkouts in parallels in a Jenkins file, the changes discovered are discovered for every git checkout that runs at the same time.

In our example, we run checkouts with 8 threads in parallel (background: https://roidelapluie.be/blog/2016/11/18/gitslave-jenkins/ ).

One change is displayed in 8 different repositories, the 8 repositories that are checked out at the same time.

The next checkouts do not contains the changes, so I guess it is kind of a global variable that is reset after the checkout.


lock('checkout') {

def checkouts = [:]
def threads = 8

stage('Super Repo') {
checkout([$class: 'GitSCM',
branches: [[name: branch]],
doGenerateSubmoduleConfigurations: false,
extensions: [[$class: 'CleanCheckout'], [$class: 'ScmName', name: 'super']],
submoduleCfg: [],
userRemoteConfigs: [[url: "${gitBaseUrl}/${superRepo}"]]])

def repos = readFile('.gitslave')
def reposLines = repos.readLines()
for (i = 0; i < threads; i++){
checkouts["Thread ${i}"] = []
}
def idx = 0
for (line in reposLines) {
def repoInfo = line.split(' ')
def repoUrl = repoInfo[0]
def repoPath = repoInfo[1]
def curatedRepoUrl = repoUrl.substring(4, repoUrl.length()-1)
def curatedRepoPath = repoPath.substring(1, repoPath.length()-1)
echo("Thread ${idx%threads}")
checkouts["Thread ${idx%threads}"] << [path: curatedRepoPath, url: "${gitBaseUrl}/${curatedRepoUrl}"]
idx++
}
}
stage('GitSlave Repos') {
def doCheckouts = [:]
for (i = 0; i < threads; i++){
def j = i
doCheckouts["Thread ${j}"] = {
for (co in checkouts["Thread ${j}"]) {
retry(3) {
checkout([$class: 'GitSCM',
branches: [[name: branch]],
doGenerateSubmoduleConfigurations: false,
extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: co.path], [$class: 'CleanCheckout'], [$class: 'ScmName', name: co.path]],
submoduleCfg: [],
userRemoteConfigs: [[url: co.url]]])
}
}
}
}
parallel doCheckouts
}


2 attachments

- [parallelcheckout.png](https://issues.jenkins.io/secure/attachment/34959/parallelcheckout.png)
> ![parallelcheckout.png](https://issues.jenkins.io/secure/attachment/34959/parallelcheckout.png)
- [parallelcheckout2.png](https://issues.jenkins.io/secure/attachment/34958/parallelcheckout2.png)
> ![parallelcheckout2.png](https://issues.jenkins.io/secure/attachment/34958/parallelcheckout2.png)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.