Skip to content

Instantly share code, notes, and snippets.

@chmouel
Created April 9, 2025 07:57
Show Gist options
  • Save chmouel/d0a3da28d6e8a006f4f5bbdea6f6f7a0 to your computer and use it in GitHub Desktop.
Save chmouel/d0a3da28d6e8a006f4f5bbdea6f6f7a0 to your computer and use it in GitHub Desktop.
git clone with merge
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
# NOTE: Changed name to avoid conflict with the original
name: git-clone
labels:
app.kubernetes.io/version: "0.10"
annotations:
tekton.dev/pipelines.minVersion: "0.38.0"
tekton.dev/categories: Git
tekton.dev/tags: git
tekton.dev/displayName: "git clone"
tekton.dev/platforms: "linux/amd64,linux/s390x,linux/ppc64le,linux/arm64"
spec:
description: >-
Clones a Git repository and optionally merges a specified target branch
(e.g., 'main') into the checked-out revision before finishing.
workspaces:
- name: output
description: The git repo will be cloned and potentially merged onto the volume backing this Workspace.
- name: ssh-directory
optional: true
description: |
A .ssh directory with private key, known_hosts, config, etc. Copied to
the user's home before git commands are executed. Used to authenticate
with the git remote when performing the clone. Binding a Secret to this
Workspace is strongly recommended over other volume types.
- name: basic-auth
optional: true
description: |
A Workspace containing a .gitconfig and .git-credentials file. These
will be copied to the user's home before any git commands are run. Any
other files in this Workspace are ignored. It is strongly recommended
to use ssh-directory over basic-auth whenever possible and to bind a
Secret to this Workspace over other volume types.
- name: ssl-ca-directory
optional: true
description: |
A workspace containing CA certificates, this will be used by Git to
verify the peer with when fetching or pushing over HTTPS.
params:
- name: url
description: Repository URL to clone from.
type: string
- name: revision
description: Revision to checkout initially (branch, tag, sha, ref, etc...).
type: string
default: ""
- name: refspec
description: Refspec to fetch before checking out revision.
default: ""
- name: submodules
description: Initialize and fetch git submodules.
type: string
default: "true"
- name: depth
description: Perform a shallow clone initially. Merging requires fetching more history later if enabled.
type: string
# Depth 1 is fast for initial checkout, but merge might need more history.
# The task attempts to fetch the target branch specifically if merging.
default: "1"
- name: sslVerify
description: Set the `http.sslVerify` global git config. Setting this to `false` is not advised unless you are sure that you trust your git remote.
type: string
default: "true"
- name: crtFileName
description: file name of mounted crt using ssl-ca-directory workspace. default value is ca-bundle.crt.
type: string
default: "ca-bundle.crt"
- name: subdirectory
description: Subdirectory inside the `output` Workspace to clone the repo into.
type: string
default: ""
- name: sparseCheckoutDirectories
description: Define the directory patterns to match or exclude when performing a sparse checkout.
type: string
default: ""
- name: deleteExisting
description: Clean out the contents of the destination directory if it already exists before cloning.
type: string
default: "true"
- name: httpProxy
description: HTTP proxy server for non-SSL requests.
type: string
default: ""
- name: httpsProxy
description: HTTPS proxy server for SSL requests.
type: string
default: ""
- name: noProxy
description: Opt out of proxying HTTP/HTTPS requests.
type: string
default: ""
- name: verbose
description: Log the commands that are executed during this Task's operation.
type: string
default: "true"
- name: gitInitImage
description: The image providing the git-init binary and git command that this Task runs.
type: string
# Ensure this image contains a standard 'git' binary as well
default: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init:v0.40.2"
- name: userHome
description: |
Absolute path to the user's home directory.
type: string
default: "/home/git"
- name: mergeTargetBranch
description: Set to "true" to merge the targetBranch into the checked-out revision.
type: string
default: "false"
- name: targetBranch
description: The target branch to merge into the revision (if mergeTargetBranch is true).
type: string
default: "main"
results:
- name: commit
description: The precise commit SHA of the revision that was initially checked out by this Task (before optional merge).
- name: url
description: The precise URL that was fetched by this Task.
- name: committer-date # Date of the original commit *before* any merge
description: The epoch timestamp of the original commit that was fetched by this Task (before optional merge).
- name: merged_sha
description: The SHA of the commit after merging the target branch (if mergeTargetBranch is true).
steps:
- name: clone
image: "$(params.gitInitImage)"
env:
- name: HOME
value: "$(params.userHome)"
- name: PARAM_URL
value: $(params.url)
- name: PARAM_REVISION
value: $(params.revision)
- name: PARAM_REFSPEC
value: $(params.refspec)
- name: PARAM_SUBMODULES
value: $(params.submodules)
- name: PARAM_DEPTH
value: $(params.depth)
- name: PARAM_SSL_VERIFY
value: $(params.sslVerify)
- name: PARAM_CRT_FILENAME
value: $(params.crtFileName)
- name: PARAM_SUBDIRECTORY
value: $(params.subdirectory)
- name: PARAM_DELETE_EXISTING
value: $(params.deleteExisting)
- name: PARAM_HTTP_PROXY
value: $(params.httpProxy)
- name: PARAM_HTTPS_PROXY
value: $(params.httpsProxy)
- name: PARAM_NO_PROXY
value: $(params.noProxy)
- name: PARAM_VERBOSE
value: $(params.verbose)
- name: PARAM_SPARSE_CHECKOUT_DIRECTORIES
value: $(params.sparseCheckoutDirectories)
- name: PARAM_USER_HOME
value: $(params.userHome)
- name: PARAM_MERGE_TARGET_BRANCH
value: $(params.mergeTargetBranch)
- name: PARAM_TARGET_BRANCH
value: $(params.targetBranch)
- name: WORKSPACE_OUTPUT_PATH
value: $(workspaces.output.path)
- name: WORKSPACE_SSH_DIRECTORY_BOUND
value: $(workspaces.ssh-directory.bound)
- name: WORKSPACE_SSH_DIRECTORY_PATH
value: $(workspaces.ssh-directory.path)
- name: WORKSPACE_BASIC_AUTH_DIRECTORY_BOUND
value: $(workspaces.basic-auth.bound)
- name: WORKSPACE_BASIC_AUTH_DIRECTORY_PATH
value: $(workspaces.basic-auth.path)
- name: WORKSPACE_SSL_CA_DIRECTORY_BOUND
value: $(workspaces.ssl-ca-directory.bound)
- name: WORKSPACE_SSL_CA_DIRECTORY_PATH
value: $(workspaces.ssl-ca-directory.path)
securityContext:
runAsNonRoot: true
runAsUser: 65532
script: |
#!/usr/bin/env sh
set -eu # Ensure script fails on error
if [ "${PARAM_VERBOSE}" = "true" ] ; then
set -x # Print commands for debugging
fi
# --- Setup Authentication and SSL (Same as original task) ---
if [ "${WORKSPACE_BASIC_AUTH_DIRECTORY_BOUND}" = "true" ] ; then
cp "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/.git-credentials" "${PARAM_USER_HOME}/.git-credentials"
cp "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/.gitconfig" "${PARAM_USER_HOME}/.gitconfig"
chmod 400 "${PARAM_USER_HOME}/.git-credentials"
chmod 400 "${PARAM_USER_HOME}/.gitconfig"
fi
if [ "${WORKSPACE_SSH_DIRECTORY_BOUND}" = "true" ] ; then
cp -R "${WORKSPACE_SSH_DIRECTORY_PATH}" "${PARAM_USER_HOME}"/.ssh
chmod 700 "${PARAM_USER_HOME}"/.ssh
chmod -R 400 "${PARAM_USER_HOME}"/.ssh/*
fi
if [ "${WORKSPACE_SSL_CA_DIRECTORY_BOUND}" = "true" ] ; then
export GIT_SSL_CAPATH="${WORKSPACE_SSL_CA_DIRECTORY_PATH}"
if [ "${PARAM_CRT_FILENAME}" != "" ] ; then
export GIT_SSL_CAINFO="${WORKSPACE_SSL_CA_DIRECTORY_PATH}/${PARAM_CRT_FILENAME}"
fi
fi
# --- Prepare Checkout Directory (Same as original task) ---
CHECKOUT_DIR="${WORKSPACE_OUTPUT_PATH}/${PARAM_SUBDIRECTORY}"
cleandir() {
if [ -d "${CHECKOUT_DIR}" ] ; then
rm -rf "${CHECKOUT_DIR:?}"/*
rm -rf "${CHECKOUT_DIR}"/.[!.]*
rm -rf "${CHECKOUT_DIR}"/..?*
fi
}
if [ "${PARAM_DELETE_EXISTING}" = "true" ] ; then
cleandir || true
fi
# --- Set Proxy (Same as original task) ---
test -z "${PARAM_HTTP_PROXY}" || export HTTP_PROXY="${PARAM_HTTP_PROXY}"
test -z "${PARAM_HTTPS_PROXY}" || export HTTPS_PROXY="${PARAM_HTTPS_PROXY}"
test -z "${PARAM_NO_PROXY}" || export NO_PROXY="${PARAM_NO_PROXY}"
# --- Initial Clone & Checkout using git-init (Same as original task) ---
git config --global --add safe.directory "${WORKSPACE_OUTPUT_PATH}" # Needed for git commands below
/ko-app/git-init \
-url="${PARAM_URL}" \
-revision="${PARAM_REVISION}" \
-refspec="${PARAM_REFSPEC}" \
-path="${CHECKOUT_DIR}" \
-sslVerify="${PARAM_SSL_VERIFY}" \
-submodules="${PARAM_SUBMODULES}" \
-depth="${PARAM_DEPTH}" \
-sparseCheckoutDirectories="${PARAM_SPARSE_CHECKOUT_DIRECTORIES}"
cd "${CHECKOUT_DIR}"
INITIAL_SHA="$(git rev-parse HEAD)" # Capture the originally checked-out commit
EXIT_CODE="$?"
if [ "${EXIT_CODE}" != 0 ] ; then
echo "ERROR: Initial git checkout failed." >&2
exit "${EXIT_CODE}"
fi
INITIAL_COMMITTER_DATE="$(git log -1 --pretty=%ct)"
if [ "${PARAM_MERGE_TARGET_BRANCH}" = "true" ]; then
echo "Merge option enabled. Attempting to merge target branch '${PARAM_TARGET_BRANCH}' into HEAD (${INITIAL_SHA})."
# Ensure we have the target branch history. Fetch it specifically.
# This might fetch more than strictly necessary if depth was > 1, but ensures it's available.
# Use standard 'git' command - assumes it's available in the image.
echo "Fetching target branch '${PARAM_TARGET_BRANCH}'..."
git fetch origin "${PARAM_TARGET_BRANCH}"
FETCH_EXIT_CODE="$?"
if [ "${FETCH_EXIT_CODE}" != "0" ]; then
echo "ERROR: Failed to fetch target branch '${PARAM_TARGET_BRANCH}'." >&2
exit "${FETCH_EXIT_CODE}"
fi
# Perform the merge using the fetched remote tracking branch
echo "Merging origin/${PARAM_TARGET_BRANCH} into current HEAD..."
# Use --no-ff to create a merge commit even if it could be fast-forwarded,
# which can make history clearer, but is optional.
git merge "origin/${PARAM_TARGET_BRANCH}" --no-commit --no-ff # Let's try --no-commit first to check for conflicts before committing
MERGE_CHECK_EXIT_CODE="$?"
if [ "${MERGE_CHECK_EXIT_CODE}" != "0" ] ; then
echo "ERROR: Merge conflict detected or merge failed before commit." >&2
echo "--- Git Status ---"
git status
echo "------------------"
# Optional: Attempt to abort the merge?
# git merge --abort
exit "${MERGE_CHECK_EXIT_CODE}"
else
# If merge is successful (no conflicts), commit the merge
echo "Merge successful (no conflicts found), committing..."
# Set committer info - adjust as needed
git config --global user.email "[email protected]"
git config --global user.name "Tekton Git Clone Task"
git commit -m "Merge branch '${PARAM_TARGET_BRANCH}' into ${INITIAL_SHA}"
COMMIT_EXIT_CODE="$?"
if [ "${COMMIT_EXIT_CODE}" != "0" ]; then
echo "ERROR: Failed to commit merge." >&2
exit "${COMMIT_EXIT_CODE}"
fi
echo "Successfully merged and committed branch '${PARAM_TARGET_BRANCH}' into initial revision."
MERGED_SHA=$(git rev-parse HEAD)
echo "New HEAD after merge: ${MERGED_SHA}"
echo "${MERGED_SHA}" > "$(results.commit.merged_sha)"
fi
else
echo "Merge option disabled. Using checked-out revision ${INITIAL_SHA} directly."
fi
# --- Write Results (Using the initial commit info) ---
# Note: Results reflect the state *before* the merge, identifying the PR commit.
# The workspace 'output' contains the potentially merged code.
printf "%s" "${INITIAL_COMMITTER_DATE}" > "$(results.committer-date.path)"
printf "%s" "${INITIAL_SHA}" > "$(results.commit.path)"
printf "%s" "${PARAM_URL}" > "$(results.url.path)"
echo "Task completed. Workspace contains code at initial revision ${INITIAL_SHA} (potentially merged with ${PARAM_TARGET_BRANCH})."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment