Pipeline - Equivalent to Git Publisher

Article ID:360027646491
4 minute readKnowledge base

Issue

  • I would like to publish changes back to my Git SCM as part of a Pipeline

Explanation

Although there is a GitPublisher step available to Freestyle jobs, there is none for Pipeline jobs. There is a known RFE for this JENKINS-28335.

There are, however, several solutions possible to push changes back to Git in Pipeline.

Resolution

there are existing solutions documented in Pipeline Examples - Push Git Repo

The main challenge is to pass authentication credentials to the push command temporarily and there are multiple options to do this with Git (see git-credentials). Also solutions are different based on requirements and scenarios (commit and push, tag and push, checkout and push using different protocol, …​).

This article provides commons solutions, recommendations and examples.

Pre-requisites

1) Configure Git User

The Git user name and email must be configured on the agent running the build to be able to commit changes / create a tag. If no user is configured in the agent environment, the following error would appear when committing / pushing changes:

*** Please tell me who you are.
Run
  git config --global user.email "you@example.com"
  git config --global user.name "Your Name"

The user / email can be set in the pipeline like the following:

[...]
sh('''
    git config user.name 'my-ci-user'
    git config user.email 'my-ci-user@users.noreply.github.example.com'
''')
[...]

(Note: The git username and email can be configured via the SCM Trait (Pipeline Multibranch) or Git Advanced Behavior (Pipeline) "Custom user name/e-mail address")

2) Checkout to a local branch

In most cases, the checkout step checkout the repository at a specific revision (in a detached HEAD). This is something to be aware of when committing and pushing changes. A good practice is to checkout to a local branch when making changes and ideally before making changes (i.e. right after the checkout) to avoid losing them when doing git operations:

git checkout -B $TARGET_BRANCH

(Note: Pipeline and Pipeline Multibranch respectively proposed the additional behavior "Check out to specific local branch" and trait "Check out to matching local branch" to checkout to a local branch right after the checkout)

Push changes to Git Repository

Changes may be push via SSH or HTTPS. It is simpler and more consistent to push via the same protocol as the one used to do the checkout.

If for any particular reason, the push must be done using a different method the URL needs to be configured accordingly:

  • Add git config url.git@github.com/.insteadOf https://github.com/ if the checkout was done through HTTPS but push must be done using SSH

  • Add git config url.https://github.com/.insteadOf git@github.com/ if the checkout was done through SSH but push must be done using HTTPS

Push via HTTPS

The Credentials Binding can be used to inject username / password (or token) as environment variable. Those variables can be used to define a credentials helper.

In scripted pipeline, use the Credentials Binding plugin wrapper. Authentication is only possible within the withCredentials block:

[...]
withCredentials([usernamePassword(credentialsId: 'my-credentials-id', usernameVariable: 'GIT_USERNAME', passwordVariable: 'GIT_PASSWORD')]){
    sh('''
        git config --local credential.helper "!f() { echo username=\\$GIT_USERNAME; echo password=\\$GIT_PASSWORD; }; f"
        git push origin HEAD:$TARGET_BRANCH
    ''')
}
[...]

In Declarative Pipeline, the environment block can be used. Authentication is only possible within the stage that defines the environment variable:

[...]
    stage('Push') {
        environment {
            GIT_AUTH = credentials('my-predefined-credentials-id')
        }
        steps {
            sh('''
                git config --local credential.helper "!f() { echo username=\\$GIT_AUTH_USR; echo password=\\$GIT_AUTH_PSW; }; f"
                git push origin HEAD:$TARGET_BRANCH
            ''')
        }
    }
[...]
}

Push via SSH

A simple solution is to use the SSH Agent plugin to inject SSH credentials via ssh-agent. For both scripted and declarative, the solution is the same:

[...]
    sshagent(['my-ssh-credentials-id']) {
        sh('''
            #!/usr/bin/env bash
            set +x
            # If no host key verification is needed, use the option `-oStrictHostKeyChecking=no`
            export GIT_SSH_COMMAND="ssh -oStrictHostKeyChecking=no"
            git push origin HEAD:\$TARGET_BRANCH
        ''')
    }
[...]

Examples

The following scripts adds a file to the current branch of a Pipeline Multibranch build and push the changes via HTTPS:

pipeline {
    agent any
    stages {
        stage("Build") {
            steps {
                // Create a dummy file in the repo
                sh('echo \$BUILD_NUMBER > example-\$BUILD_NUMBER.md')
            }
        }
        stage("Commit") {
            steps {
                sh('''
                    git checkout -B $TARGET_BRANCH
                    git config user.name 'my-ci-user'
                    git config user.email 'my-ci-user@users.noreply.github.example.com'
                    git add . && git commit -am "[Jenkins CI] Add build file"
                ''')
            }
        }
        stage("Push") {
            environment {
                GIT_AUTH = credentials('support-team-up')
            }
            steps {
                sh('''
                    git config --local credential.helper "!f() { echo username=\\$GIT_AUTH_USR; echo password=\\$GIT_AUTH_PSW; }; f"
                    git push origin HEAD:$TARGET_BRANCH
                ''')
            }
        }
    }
}

The following script creates a tag of the current branch of a Pipeline Multibranch build push it via SSH:

pipeline {
    agent any
    stages {
        stage("Tag and Push") {
            when { branch 'main' }
            environment {
                GIT_TAG = "jenkins-$BUILD_NUMBER"
            }
            steps {
                sh('''
                    git config user.name 'my-ci-user'
                    git config user.email 'my-ci-user@users.noreply.github.example.com'
                    git tag -a \$GIT_TAG -m "[Jenkins CI] New Tag"
                ''')

                sshagent(['my-ssh-credentials-id']) {
                    sh("""
                        #!/usr/bin/env bash
                        set +x
                        export GIT_SSH_COMMAND="ssh -oStrictHostKeyChecking=no"
                        git push origin \$GIT_TAG
                     """)
                }
            }
        }
    }
}