Chapter 06Lesson 02~135 minutes

Source Control Integration, Git Plugin, Credentials, Polling, Webhooks, and Multirepository Checkout: Guided Hands-On Workflow and Core Operations

Use a disposable synthetic Git service to perform Freestyle and Pipeline checkouts, prove immutable source identity, compare polling with token-protected notification, and isolate a second repository.

Hands-on GitFreestyle checkoutPipeline checkoutnotifyCommitMulti-repo

Learning objectives

  • Create two synthetic repositories without using production source or real credentials.
  • Checkout source through Freestyle and minimal Pipeline jobs and verify the exact workspace SHA.
  • Compare scheduled SCM polling with a token-protected Git plugin notification.
  • Capture repository, cause, plugin/tool, agent, and changelog evidence before changing configuration.
  • Checkout a second repository into a bounded directory and record a separate provenance identity.

1. Lab boundary and assumptions

This workflow uses a synthetic Git service reachable only inside a disposable lab network or disposable VM. The mandatory path requires no real SCM account and no real secret. If you already have the Chapter 05 disposable controller and agent, reuse them; otherwise create equivalent isolated resources. Routine builds remain off the built-in node.

  • Jenkins core: 2.568.3 LTS, Java 21 or 25; examples assume Java 21.
  • Git plugin: 5.10.1; Git Client: 6.6.1; Credentials: 1511.v2e3cb_0008ef0.
  • A disposable agent labeled linux with command-line Git installed.
  • A synthetic Git endpoint reachable by both Jenkins polling logic and the build agent. For same-host process labs, git://127.0.0.1:9418 is acceptable; container labs should use a dedicated lab network hostname such as scm-lab.
  • No production repository, no real token, and no public webhook endpoint.
Network note: if controller and agent run in different containers, 127.0.0.1 points to different containers. Use a dedicated disposable network service name instead. Do not work around connectivity by exposing Jenkins or the Git service to the public internet.

2. Seed two synthetic repositories

On the disposable Git-service host, create two working repositories and bare remotes. The first represents application source; the second represents shared documentation/configuration. The writer workspace is outside Jenkins so you can create controlled commits and watch Jenkins react.

set -eu
LAB_ROOT="$HOME/jenkins-scm-lab"
rm -rf "$LAB_ROOT"
mkdir -p "$LAB_ROOT"/{writer,remotes}

seed_repo() {
  name="$1"
  mkdir -p "$LAB_ROOT/writer/$name"
  git -C "$LAB_ROOT/writer/$name" init -b main
  git -C "$LAB_ROOT/writer/$name" config user.name "SCM Lab Writer"
  git -C "$LAB_ROOT/writer/$name" config user.email "scm-lab@example.invalid"
  printf '%s\n' "$name version=1" > "$LAB_ROOT/writer/$name/README.txt"
  git -C "$LAB_ROOT/writer/$name" add README.txt
  git -C "$LAB_ROOT/writer/$name" commit -m "seed $name"
  git init --bare "$LAB_ROOT/remotes/$name.git"
  git -C "$LAB_ROOT/writer/$name" remote add origin "$LAB_ROOT/remotes/$name.git"
  git -C "$LAB_ROOT/writer/$name" push -u origin main
}

seed_repo app
seed_repo docs
printf 'app=%s\n' "$(git -C "$LAB_ROOT/writer/app" rev-parse HEAD)"
printf 'docs=%s\n' "$(git -C "$LAB_ROOT/writer/docs" rev-parse HEAD)"

Start a lab-only Git daemon bound to the disposable interface. On a same-host lab:

git daemon   --reuseaddr   --export-all   --base-path="$LAB_ROOT/remotes"   --listen=127.0.0.1   --port=9418   "$LAB_ROOT/remotes"

Leave this terminal open. In a container lab, run an equivalent dedicated Git service on the lab bridge network and use its internal hostname. The daemon path here is intentionally read-only from Jenkins; controlled source changes are pushed from the writer workspace into the bare repositories.

3. Preflight before creating the Jenkins job

From the Jenkins agent context, verify connectivity and Git identity without cloning yet. Use the hostname appropriate to your lab.

APP_URL="git://127.0.0.1:9418/app.git"
DOCS_URL="git://127.0.0.1:9418/docs.git"

git --version
git ls-remote "$APP_URL" refs/heads/main
git ls-remote "$DOCS_URL" refs/heads/main

The output gives a remote SHA for each main branch. Record those values as pre-build observations, not as proof of what a later Jenkins build checked out. A commit can change between preflight and checkout; the workspace SHA after checkout is the evidence that matters.

4. Freestyle checkout: configuration and evidence

Create a disposable Freestyle project named scm-freestyle-lab. Restrict it to label linux. Under Source Code Management choose Git, enter the synthetic APP_URL, leave Credentials at none, and set the branch specifier to */main. The no-credential choice is intentional evidence: this repository is public only inside the lab and the job has no secret authority.

Add an Execute shell build step:

set -eu
printf 'job=%s build=%s node=%s workspace=%s\n'   "$JOB_NAME" "$BUILD_NUMBER" "$NODE_NAME" "$WORKSPACE"
printf 'configured_git_commit=%s\n' "${GIT_COMMIT:-unset}"
printf 'remote=%s\n' "$(git remote get-url origin)"
printf 'head=%s\n' "$(git rev-parse HEAD)"
printf 'branch=%s\n' "$(git symbolic-ref --short -q HEAD || printf detached)"
printf 'git=%s\n' "$(git --version)"

git show -s --format='commit=%H%ncommit_time=%cI%nsubject=%s' HEAD
mkdir -p evidence
git rev-parse HEAD > evidence/app.sha256-input.txt
git remote get-url origin > evidence/app.remote.txt

Archive evidence/* as build artifacts. After the first run, compare GIT_COMMIT with git rev-parse HEAD. They should identify the same checked-out commit. If they do not, preserve the console log and configuration before changing anything.

5. Create a controlled source change

From the writer workspace, commit and push one change. Capture the new SHA before asking Jenkins to evaluate it.

LAB_ROOT="$HOME/jenkins-scm-lab"
printf '%s\n' "app version=2" > "$LAB_ROOT/writer/app/README.txt"
git -C "$LAB_ROOT/writer/app" add README.txt
git -C "$LAB_ROOT/writer/app" commit -m "app: controlled SCM change"
git -C "$LAB_ROOT/writer/app" push origin main
NEW_APP_SHA="$(git -C "$LAB_ROOT/writer/app" rev-parse HEAD)"
printf 'new_app_sha=%s\n' "$NEW_APP_SHA"

Do not click Build Now yet. The point is to compare Jenkins' SCM-trigger mechanisms against a known repository state change.

6. Observe polling without creating an unbounded schedule

Enable Poll SCM on the Freestyle project. For a bounded observation you may use a temporary schedule such as H/5 * * * *, then disable it after one observed polling cycle. Record the polling log and whether Jenkins schedules a build. A poll is an evaluation activity; the resulting build is a separate queue/run state.

Do not leave the lab timer enabled. The purpose is to observe the state transition once, not to create a permanent recurring load source.

If waiting for the schedule is impractical, keep Poll SCM enabled with no schedule and use the token-protected notification in the next section. The Git plugin can use that notification to invoke the polling logic immediately.

7. Simulate a local webhook with token-protected notifyCommit

On the disposable controller, create a Git plugin notifyCommit access token through the current security configuration. Store the value locally as an environment variable; do not place it in the job, console log, shell history, screenshot, or evidence archive. Keep the plugin's default token protection enabled.

export JENKINS_URL='http://127.0.0.1:8080'
export APP_URL='git://127.0.0.1:9418/app.git'
TOKEN_FILE="$HOME/.jenkins-scm-lab-notify-token"
umask 077
read -rsp 'Lab notifyCommit token: ' token; printf '\n'
printf '%s' "$token" > "$TOKEN_FILE"
unset token

curl --fail --get   --data-urlencode "url=$APP_URL"   --data-urlencode "token@$TOKEN_FILE"   "$JENKINS_URL/git/notifyCommit"
rm -f "$TOKEN_FILE"

A successful notification should cause Jenkins to poll matching jobs that have Poll SCM enabled. If the repository has a new commit that qualifies, Jenkins then schedules a build. Capture the returned project list, polling log, queue/build cause, and resulting workspace SHA. Redact the token from all retained evidence.

8. Perform the same checkout in a minimal Pipeline preview

Pipeline is taught in depth later; here it is used only to compare SCM evidence. Create a disposable Pipeline job scm-pipeline-lab and run it on the linux agent. Use the explicit checkout + scmGit form because it exposes repository and branch intent clearly.

node('linux') {
  stage('Checkout app') {
    deleteDir()
    def scmVars = checkout scmGit(
      branches: [[name: '*/main']],
      userRemoteConfigs: [[url: 'git://127.0.0.1:9418/app.git']]
    )
    echo "plugin_commit=${scmVars.GIT_COMMIT}"
    sh '''
      set -eu
      printf 'remote=%s\n' "$(git remote get-url origin)"
      printf 'head=%s\n' "$(git rev-parse HEAD)"
      git show -s --format='commit=%H commit_time=%cI subject=%s' HEAD
    '''
  }
}

The returned scmVars.GIT_COMMIT and workspace git rev-parse HEAD should agree. This is stronger evidence than a branch label because both point to an immutable Git object.

9. Checkout a second repository into a bounded directory

Extend the Pipeline preview so application and documentation sources cannot overwrite each other. Capture each identity independently.

node('linux') {
  deleteDir()

  dir('app') {
    checkout scmGit(
      branches: [[name: '*/main']],
      userRemoteConfigs: [[url: 'git://127.0.0.1:9418/app.git']]
    )
    sh "git remote get-url origin > ../app.remote && git rev-parse HEAD > ../app.sha"
  }

  dir('docs') {
    checkout scmGit(
      branches: [[name: '*/main']],
      userRemoteConfigs: [[url: 'git://127.0.0.1:9418/docs.git']]
    )
    sh "git remote get-url origin > ../docs.remote && git rev-parse HEAD > ../docs.sha"
  }

  sh '''
    printf 'app  repo=%s sha=%s\n' "$(cat app.remote)" "$(cat app.sha)"
    printf 'docs repo=%s sha=%s\n' "$(cat docs.remote)" "$(cat docs.sha)"
  '''
}

Do not use the same directory for two repositories unless replacement is intentional and explicitly cleaned. Separate directories make provenance, cleanup, and trust boundaries reviewable.

10. Credential scope exercise without a real secret

The mandatory local Git daemon needs no authentication. Use that fact to prove a valuable negative: both lab SCM configurations should have no credential selected. Then inspect the Jenkins credential UI from Chapter 04 and answer where a repository-specific credential would live if this were a protected remote. Do not create or export a real token merely to satisfy the exercise.

Optional extension: on a fully disposable local Git service that supports authentication, create a fake lab-only account and folder-scoped credential. Verify that only the intended folder/job can select it. Do not substitute a personal GitHub/GitLab credential for this exercise.

11. Verification and cleanup

  • Disable any temporary Poll SCM schedule.
  • Preserve build numbers, causes, polling logs, repository URLs, and exact SHAs for the observed runs.
  • Verify scm-freestyle-lab and scm-pipeline-lab used the linux agent, not the built-in node.
  • Stop the disposable Git daemon/service.
  • Delete only the synthetic jenkins-scm-lab directory and lab jobs after confirming their exact identities.
  • Do not delete shared plugins or credentials as “cleanup.”
Next lesson

Configuration, Design Choices, and Tradeoffs

Choose deliberately between polling and webhooks, HTTPS and SSH credentials, implicit and explicit checkout, monorepository and multirepository designs, and host-key trust strategies.

Knowledge check

Why does the mandatory lab use no SCM credential?

What is the key evidence after a checkout?

Why keep Poll SCM enabled for notifyCommit even with no schedule?

Why should app and docs repositories use separate directories?

May the lab disable notifyCommit access control to simplify curl?

Official references and version notes

Version and compatibility note

Rechecked on 2026-09-15. Chapter examples use Jenkins 2.568.3 LTS (tested with Java 21 and 25), Git plugin 5.10.1, Git Client plugin 6.6.1, and Credentials plugin 1511.v2e3cb_0008ef0. The mandatory path assumes a disposable Java 21 controller/agent lab and records the actual command-line Git version from the agent rather than freezing a universal Git binary version. Git/credentials/plugin behavior and security advisories evolve; re-check primary Jenkins/plugin documentation before reusing these exact versions or settings.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.