| # shellcheck shell=bash |
| # Copyright 2018-2023 Fairphone B.V. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| |
| # repo forall -c environment: Refer to the `repo` tool documentation for details |
| # about REPO_* variables that are used below. |
| # |
| # |
| # Globals: |
| # SHELL_LIBS |
| |
| set -e |
| set -u |
| |
| # Includes: |
| . "${SHELL_LIBS}/utils.sh" |
| |
| |
| # Environment variables: |
| DEBUG="${DEBUG:-}" |
| DRY_RUN="${DRY_RUN:-false}" # If set to true, run ssh/git commands without modify remote. |
| |
| |
| # Bail out if the current project is git-repo or platform/manifest |
| # |
| # When running on a mirror workspace, git-repo and the upstream |
| # platform/manifest project both show up in the workspace. Usually we don't want |
| # to touch them; repo forall commands should just skip them. To do so, call this |
| # function early in the 'repo forall' command. |
| # |
| # Globals: |
| # REPO_PROJECT |
| skip_repo_mirror_internal_project() |
| { |
| case "${REPO_PROJECT}" in |
| # AOSP tooling and manifest |
| git-repo|platform/manifest) |
| exit 0 |
| ;; |
| # Qualcomm/CodeLinaro manifests |
| la/system/manifest|la/vendor/manifest) |
| exit 0 |
| ;; |
| # Fairphone Gerrit manifest |
| manifest) |
| exit 0 |
| ;; |
| esac |
| } |
| |
| # Check if a reference exists on project remote. |
| # |
| # Globals: |
| # REPO_REMOTE |
| # Arguments: |
| # ref The reference to check. |
| # Returns: |
| # 0 if found, otherwise the exit code for 'git ls-remote' |
| _remote_ref_exists() |
| { |
| local ref="$1" |
| |
| local result=0 |
| git ls-remote --exit-code "${REPO_REMOTE}" "${ref}" > /dev/null 2>&1 \ |
| || result=$? |
| |
| case $result in |
| 0) |
| ;; |
| 2) |
| log_e "${ref} does not exist on remote '${REPO_REMOTE}'" |
| ;; |
| 128) |
| log_e "${REPO_REMOTE} is not a valid remote" |
| ;; |
| *) |
| log_e "[$result]: Unable to 'git ls-remote ${REPO_REMOTE} ${ref}'" |
| ;; |
| esac |
| |
| return "${result}" |
| } |
| |
| # Check and optionally qualify a reference |
| # |
| # Check if a reference is a fully qualified branch or tag. If |
| # TRY_QUALIFYING_REFS is enabled, try resolving unqualified via querying on the |
| # remote. This will fail if the reference exists both as branch and tag (or if |
| # it does not exist). |
| # |
| # Globals: |
| # REPO_REMOTE |
| # TRY_QUALIFYING_REFS |
| # Arguments: |
| # ref The reference to check and qualify. |
| # Returns: |
| # 0 if found, otherwise 1 |
| # If found, the fully qualified name gets posted to stdout. Other messages are |
| # posted to stderr. |
| _qualify_remote_ref() |
| { |
| local ref="$1" |
| if [ "${TRY_QUALIFYING_REFS}" != true ]; then |
| # Disabled, skip the logic and leave ref as it is. |
| echo "${ref}" |
| return 0 |
| fi |
| |
| # Must start with refs/heads/ or refs/tags/. Ignore special refs |
| # (refs/changes/ etc from Gerrit), we shouldn't see them in manifests. |
| |
| if echo "${ref}" | grep -qEe '^refs\/(heads|tags)\/'; then |
| # All good, already fully qualified. |
| echo "${ref}" |
| return 0 |
| fi |
| |
| if echo "${ref}" | grep -qEe '^refs\/'; then |
| # Don't deal with special refs; just fail early. |
| log_e "${ref} is a special ref, not a branch or tag." |
| fi |
| |
| local result=0 |
| local qualified_refs |
| # Don't use `--exit-code`; we'll check below if there are matching refs. |
| qualified_refs=$(git ls-remote "${REPO_REMOTE}" \ |
| "refs/heads/${ref}" "refs/tags/${ref}" 2>/dev/null) || result=$? |
| |
| if [ "${result}" -ne 0 ]; then |
| log_e "[${result}]: Unable to 'git ls-remote' on ${REPO_REMOTE}." |
| fi |
| |
| # Skip the revision sha1 column |
| qualified_refs=$(echo "${qualified_refs}" | awk '{ print $2 }') |
| |
| # If the ref does not exist or is ambiguous, return 1 and let the caller |
| # handle the situation. |
| local num_matching_refs |
| num_matching_refs=$(echo "${qualified_refs}" | wc -l) |
| case "${num_matching_refs}" in |
| 0) |
| log_w "No matching remote refs unqualified ref \"${ref}\"." >&2 |
| return 1 |
| ;; |
| 1) |
| log_i "Resolving unqualified ref \"${ref}\" to" \ |
| "\"${qualified_refs}\"." >&2 |
| echo "${qualified_refs}" |
| return 0 |
| ;; |
| *) |
| log_w "Unqualified ref \"${ref}\" is ambiguous." \ |
| "Found matching: ${qualified_refs}" >&2 |
| return 1 |
| ;; |
| esac |
| } |
| |
| |
| # Check if a reference exists. |
| # |
| # Globals: |
| # None |
| # Arguments: |
| # ref The explicit reference path to check. |
| # Returns: |
| # Exit code of 'git show-ref' |
| _local_ref_exists() |
| { |
| local ref="$1" |
| |
| local result=0 |
| git show-ref --quiet --verify "${ref}" || result=$? |
| |
| return "${result}" |
| } |
| |
| # Check if the current project is a git mirror. |
| # |
| # Globals: |
| # None |
| # Arguments: |
| # None |
| # Returns: |
| # 0 if it's a mirror, 1 otherwise. |
| _project_is_mirror() |
| { |
| local is_mirror="" |
| # We can safely assume that if `git config` reports failure, it's because we are not on a |
| # mirror, and .mirror is just not set. |
| is_mirror=$(git config --local --get core.bare || true) |
| |
| if [ "${is_mirror}" = true ]; then |
| return 0 |
| else |
| return 1 |
| fi |
| } |
| |
| # Resolve name of local copy of remote branch |
| # |
| # Globals: |
| # REPO_REMOTE |
| # Arguments: |
| # branch Unqualified branch name. |
| # Returns: |
| # Fully qualified reference to remote branch name gets posted to stdout. |
| _resolve_remote_branch() |
| { |
| local branch_name="$1" |
| local ref |
| if _project_is_mirror; then |
| # mirrors have no "remote" section, but instead a direct copy of the |
| # upstream repository. |
| ref="refs/heads/${branch_name}" |
| else |
| # Non-mirrors need to point to the upstream branch. |
| ref="refs/remotes/${REPO_REMOTE}/${branch_name}" |
| fi |
| |
| if ! _local_ref_exists "${ref}"; then |
| log_e "Unable to find branch '${branch_name}' in local repository (expected to find reference ${ref})." |
| fi |
| echo "${ref}" |
| } |
| |
| |
| # Fetch a reference from a remote. |
| # |
| # If we have a shallow copy of the project, remove this condition. |
| # |
| # Globals: |
| # REPO_PROJECT |
| # REPO_REMOTE |
| # Arguments: |
| # ref_spec Specifies which refs to fetch and which local refs to update. |
| # Returns: |
| # None |
| _git_fetch() |
| { |
| local ref_spec="$1" |
| local args=() |
| |
| # Update shallow copies if needed |
| if [ -f .git/shallow ] ; then |
| log_i "Removing shallow clone for ${REPO_PROJECT}" |
| args+=(--unshallow) |
| fi |
| |
| local result=0 |
| git fetch --no-tags "${args[@]}" "${REPO_REMOTE}" "${ref_spec}" || result=$? |
| |
| case $result in |
| 0) |
| ;; |
| *) |
| log_e "[$result]: Error fetching ${ref_spec} from ${REPO_REMOTE}." |
| ;; |
| esac |
| } |
| |
| |
| # Push a reference to the remote gerrit. |
| # |
| # Globals: |
| # DRY_RUN |
| # PUSH_SKIP_VALIDATION |
| # TARGET_GERRIT_NAME |
| # Arguments: |
| # ref_spec Specify what destination ref to update with what source object. |
| # Returns: |
| # None |
| _git_push() |
| { |
| local src_ref="${1:-}" |
| local target_ref="${2:-}" |
| if [ -z "${src_ref}" ]; then |
| log_e "Source reference is empty, which would lead to delete on remote. Aborting." |
| elif [ -z "${target_ref}" ]; then |
| log_e "Target reference is empty." |
| fi |
| local ref_spec="${src_ref}:${target_ref}" |
| |
| local args=() |
| if [ "${DRY_RUN}" = true ]; then |
| args+=(--dry-run) |
| fi |
| if [ "${PUSH_SKIP_VALIDATION}" = true ]; then |
| args+=(-o skip-validation) |
| fi |
| |
| local result=0 |
| git push "${args[@]}" "${TARGET_GERRIT_NAME}" "${ref_spec}" || result=$? |
| |
| case $result in |
| 0) |
| ;; |
| 128) |
| remote_url="$(git config --get "remote.${TARGET_GERRIT_NAME}.url")" |
| log_e "${remote_url} is not a valid remote." |
| ;; |
| *) |
| log_e "[$result]: Error pushing ${ref_spec} to" \ |
| "${TARGET_GERRIT_NAME}." |
| ;; |
| esac |
| } |
| |
| |
| # Run a gerrit command on the remote gerrit server. |
| # |
| # Globals: |
| # TARGET_GERRIT_PORT |
| # TARGET_GERRIT_URL |
| # Arguments: |
| # cmd The gerrit command |
| # Returns: |
| # Result of the gerrit command |
| _ssh_gerrit() |
| { |
| local result=0 |
| ssh -p "${TARGET_GERRIT_PORT}" "${TARGET_GERRIT_URL}" gerrit "$@" \ |
| || result=$? |
| |
| case $result in |
| 0) |
| ;; |
| *) |
| log_e "[$result]: Error running 'ssh gerrit $*'" |
| ;; |
| esac |
| |
| return "${result}" |
| } |
| |
| |
| # Add remote gerrit to local git project if missing. |
| # |
| # Globals: |
| # TARGET_GERRIT_NAME |
| # TARGET_GERRIT_PORT |
| # TARGET_GERRIT_URL |
| # REPO_PROJECT |
| # Arguments: |
| # None |
| # Returns: |
| # None |
| add_git_remote() |
| { |
| if [ -z "$(git config --get "remote.${TARGET_GERRIT_NAME}.url")" ] ; then |
| log_i "Adding remote '${TARGET_GERRIT_NAME}' to ${REPO_PROJECT}" |
| local gerrit="ssh://${TARGET_GERRIT_URL}:${TARGET_GERRIT_PORT}" |
| git remote add "${TARGET_GERRIT_NAME}" "${gerrit}/${REPO_PROJECT}" |
| fi |
| } |
| |
| |
| # Add missing project to remote gerrit if missing. |
| # |
| # Globals: |
| # TARGET_GERRIT_URL |
| # REPO_PROJECT |
| # DRY_RUN |
| # Arguments: |
| # None |
| # Returns: |
| # None |
| add_project_to_gerrit() |
| { |
| if [ -z "${TARGET_GERRIT_PARENT_PROJECT}" ]; then |
| # Don't set a default for this rather dangerous parameter. It must be |
| # set in the calling code. |
| log_e "TARGET_GERRIT_PARENT_PROJECT not defined." |
| fi |
| # Does project exist on remote? |
| if [ -n "$(_ssh_gerrit ls-projects -r "^${REPO_PROJECT}$")" ]; then |
| return |
| fi |
| |
| if [ "${DRY_RUN}" != true ] ; then |
| local result=0 |
| _ssh_gerrit create-project "${REPO_PROJECT}" \ |
| -p "${TARGET_GERRIT_PARENT_PROJECT}" || result=$? |
| if [ $result -eq 0 ] ; then |
| log_i " ${REPO_PROJECT} added to ${TARGET_GERRIT_URL}" |
| fi |
| else |
| # No dry-run options exists for 'gerrit create-project' |
| log_i "ssh gerrit create-project ${REPO_PROJECT} -p" \ |
| "${TARGET_GERRIT_PARENT_PROJECT}" |
| fi |
| } |
| |
| |
| # Fetch and save a reference from a remote. |
| # |
| # If we have a shallow copy, remove this condition. In case of tags, a local |
| # copy of the remote tag is create. Branches just keep referring to the remote |
| # branch. Verbose error checks are done in case of failures. |
| # |
| # Globals: |
| # REPO_PROJECT |
| # REPO_REMOTE |
| # Arguments: |
| # from_ref The explicit ref path of the ref to fetch. |
| # Returns: |
| # None |
| fetch_remote_ref() |
| { |
| local from_ref=$1 |
| local ref_spec |
| if [[ "${from_ref}" == refs/tags/* ]]; then |
| # Create local copy of remote tag |
| ref_spec="${from_ref}:${from_ref}" |
| elif [[ "${from_ref}" == refs/heads/* ]]; then |
| # Fetch via branch name. This create a local reference under the remote. |
| ref_spec="${from_ref#refs/heads/}" |
| else |
| log_e "Invalid source reference: \"${from_ref}\"" |
| fi |
| |
| # Try fetching first. In case of failures, use `git ls-remote` to check for |
| # details. Don't always call it; otherwise upstream will block connections |
| # because of too many requests. |
| ( |
| # _git_fetch exits in case of errors, so run it in a sub shell. |
| _git_fetch "${ref_spec}" |
| ) |
| |
| local result=$? |
| if [ "${result}" -ne 0 ]; then |
| # Try getting some details on what went wrong: |
| _remote_ref_exists "${from_ref}" || true |
| fi |
| } |
| |
| |
| # Fetch a branch from a remote. |
| # |
| # Globals: |
| # REPO_PROJECT |
| # REPO_REMOTE |
| # Arguments: |
| # branch |
| # Returns: |
| # None |
| fetch_remote_branch() |
| { |
| local branch="$1" |
| [ -n "${branch}" ] || log_e "Missing branch to fetch" |
| |
| fetch_remote_ref "refs/heads/${branch}" |
| } |
| |
| |
| # Fetch a tag from a remote. |
| # |
| # Globals: |
| # REPO_PROJECT |
| # REPO_REMOTE |
| # Arguments: |
| # tag |
| # Returns: |
| # None |
| fetch_remote_tag() |
| { |
| local tag="$1" |
| [ -n "${tag}" ] || log_e "Missing tag to fetch" |
| |
| fetch_remote_ref "refs/tags/${tag}" |
| } |
| |
| |
| # Globals: |
| # REPO_PROJECT |
| # REPO_REMOTE |
| # REPO_RREV |
| # Arguments: |
| # None |
| # Returns: |
| # None |
| remove_shallow_clone() |
| { |
| # Update shallow copies if needed |
| if [ -f .git/shallow ] ; then |
| _git_fetch "${REPO_RREV}:${REPO_RREV}" |
| fi |
| } |
| |
| # Create a new, optionally annotated git tag locally |
| # |
| # Globals: |
| # NEW_TAG_ANNOTATION_MESSAGE |
| # Optional annotation for newly created tags. |
| # Arguments: |
| # tag Name of newly created tag. |
| # Returns: |
| # None |
| create_new_tag() |
| { |
| local tag="${1:-}" |
| if [ -z "${tag}" ]; then |
| log_e 'Missing tag parameter' |
| fi |
| |
| local extra_params=() |
| if [ -n "${NEW_TAG_ANNOTATION_MESSAGE}" ]; then |
| log_i "Creating tag with annotation: ${NEW_TAG_ANNOTATION_MESSAGE}" |
| extra_params+=( |
| --annotate |
| -m "${NEW_TAG_ANNOTATION_MESSAGE}" |
| ) |
| fi |
| |
| git tag "${extra_params[@]}" "${tag}" |
| } |
| |
| |
| # Push a reference to the remote gerrit. |
| # |
| # Push either a remote (upstream) source branch or the current HEAD to the |
| # target gerrit. |
| # |
| # Globals: |
| # DRY_RUN |
| # PUSH_SKIP_VALIDATION |
| # REPO_REMOTE |
| # TARGET_GERRIT_NAME |
| # Arguments: |
| # branch_src Name of the remote branch to push, or "HEAD". |
| # branch_target Name of the target branch to push to. |
| # Returns: |
| # None |
| push_branch_to_gerrit() |
| { |
| local branch_src="${1:-}" |
| local branch_target="${2:-}" |
| |
| [ -n "${branch_src}" ] || log_e 'Missing branch parameter' |
| [ -n "${branch_target}" ] || log_e 'Missing target branch parameter' |
| |
| local from_ref= |
| local to_ref="refs/heads/${branch_target}" |
| |
| if [ "${branch_src}" = "HEAD" ]; then |
| # Push the current HEAD. On mirrored repositories, `repo sync` updates |
| # HEAD as well (even there is no working directory), thus we can use |
| # HEAD for both regular tree and mirrors. |
| from_ref=HEAD |
| else |
| from_ref=$(_resolve_remote_branch "${branch_src}") |
| fi |
| |
| if [ -n "${from_ref}" ] ; then |
| _git_push "${from_ref}" "${to_ref}" |
| fi |
| } |
| |
| |
| # Push a tag to the remote gerrit. |
| # |
| # Globals: |
| # DRY_RUN |
| # PUSH_SKIP_VALIDATION |
| # TARGET_GERRIT_NAME |
| # Arguments: |
| # Tag Name of the tag to push. |
| # Returns: |
| # None |
| push_tag_to_gerrit() |
| { |
| local tag="${1:-}" |
| if [ -z "${tag}" ]; then |
| log_e 'Missing tag parameter' |
| fi |
| |
| local tag_ref="refs/tags/${tag}" |
| local result=0 |
| _local_ref_exists "${tag_ref}" || result=$? |
| if [ $result -eq 0 ] ; then |
| _git_push "${tag_ref}" "${tag_ref}" |
| fi |
| } |
| |
| |
| # Fetch tag, or create locally if not available on the source gerrit. Then push |
| # to the target gerrit. |
| # |
| # Globals: |
| # NEW_TAG_ANNOTATION_MESSAGE |
| # PUSH_SKIP_VALIDATION |
| # REPO_PROJECT |
| # REPO_REMOTE |
| # TARGET_GERRIT_NAME |
| # VERBOSE |
| # Arguments: |
| # Tag Name of the tag to push. |
| # Returns: |
| # None |
| fetch_and_push_tag_to_gerrit_fall_back_to_new_tag() |
| { |
| local tag="$1" |
| if [ -z "${tag}" ]; then |
| log_e 'Missing tag parameter' |
| fi |
| |
| # Can can have the following cases: |
| # 1. Source gerrit has the tag, target gerrit may or may not have it. |
| # -> Fetch tag and push to target. That way we will see if the target |
| # gerrit might have a conflicting version of that tag. |
| # -> If tags on source and target gerrit conflict, we just fail. To be |
| # checked manually what to do. |
| # 2. Source gerrit does not, but target gerrit has the tag. |
| # -> Nothing more to do, just assume the tag is valid. |
| # 3. None of the gerrits has the tag. |
| # -> Apply the workaround: Create the tag locally. |
| |
| local tag_ref="refs/tags/${tag}" |
| local source_fetch_result=0 |
| # Run in sub-shell to catch usually expected abort on fetch failure. |
| ( |
| fetch_remote_tag "${tag}" |
| ) || source_fetch_result=$? |
| |
| if [ "${source_fetch_result}" -eq 0 ]; then |
| # Case 1 |
| log_i "Fetch succeeded. Skipping work around with locally created tag." |
| fi |
| |
| # Case 2 or 3 |
| if [ "${source_fetch_result}" -ne 0 ]; then |
| log_i "Fetch failed. Checking for tag on target gerrit." |
| local target_fetch_result=0 |
| ( |
| # Modifying global REPO_REMOTE within sub-shell is safe; it won't |
| # propagate back to us. |
| # shellcheck disable=SC2030 |
| export REPO_REMOTE="${TARGET_GERRIT_NAME}" |
| fetch_remote_tag "${tag}" |
| ) || target_fetch_result=$? |
| if [ "${target_fetch_result}" -eq 0 ]; then |
| # Case 2: Keep the tag on the target gerrit. Nothing more to do. |
| log_i "Tag already present on target gerrit. Done." |
| return 0 |
| fi |
| |
| # Case 3 |
| log_w "[WORKAROUND] Neither source nor target gerrit have tag ${tag}." \ |
| "Creating the tag locally." |
| create_new_tag "${tag}" |
| fi |
| |
| # Case 1 or 3 wrap up: Sanity check on local ref and push to target. |
| if ! _local_ref_exists "${tag_ref}"; then |
| log_e "[assert] Tag ${tag} does not exist locally when it should now." |
| fi |
| # This might fail if the target gerrit has a conflicting version of the tag. |
| _git_push "${tag_ref}" "${tag_ref}" |
| } |
| |
| # Push a known reference from source to target |
| # |
| # This unifies pushing any known reference, possibly coming from per-project manifest attributes. |
| # References are qualified if enabled (TRY_QUALIFYING_REFS), unshallowed if necessary, and branches |
| # get prefixed with PUSH_BRANCH_PREFIX if enabled. |
| # |
| # Globals: |
| # DRY_RUN |
| # PUSH_BRANCH_PREFIX |
| # PUSH_SKIP_VALIDATION |
| # REPO_PROJECT |
| # REPO_REMOTE |
| # TARGET_GERRIT_NAME |
| # TRY_QUALIFYING_REFS |
| # Arguments: |
| # None |
| # Returns: |
| # None |
| _push_reference() |
| { |
| local ref_parm="$1" |
| local result=0 |
| reference=$(_qualify_remote_ref "${ref_parm}") || result=$? |
| if [ "${result}" -ne 0 ]; then |
| log_e \ |
| "Cannot push on project ${REPO_PROJECT}: Cannot resolve target reference ${ref_parm}." |
| fi |
| |
| # Fetch and unshallow if necessary. |
| fetch_remote_ref "${reference}" |
| |
| local src_ref |
| local target_ref |
| if [[ "${reference}" == refs/tags/* ]]; then |
| src_ref="${reference}" |
| target_ref="${reference}" |
| elif [[ "${reference}" == refs/heads/* ]]; then |
| local branch_name="${reference/refs\/heads\//}" |
| src_ref=$(_resolve_remote_branch "${branch_name}") |
| target_ref="refs/heads/${PUSH_BRANCH_PREFIX}${branch_name}" |
| else |
| log_e "Can't resolve source ref from ${ref_parm}, resolved to ${reference}." |
| fi |
| |
| _git_push "${src_ref}" "${target_ref}" |
| } |
| |
| # Push the remote revision that the manifest is currently pointing to. |
| # |
| # Globals: |
| # DRY_RUN |
| # PUSH_BRANCH_PREFIX |
| # PUSH_SKIP_VALIDATION |
| # REPO_PROJECT |
| # REPO_REMOTE |
| # REPO_RREV |
| # TARGET_GERRIT_NAME |
| # TRY_QUALIFYING_REFS |
| # Arguments: |
| # None |
| # Returns: |
| # None |
| push_current_remote_revision() |
| { |
| if [ -z "${REPO_RREV:-}" ]; then |
| log_e \ |
| "REPO_RREV is not set in project ${REPO_PROJECT}. This should never be the case." |
| fi |
| |
| _push_reference "${REPO_RREV}" |
| } |
| |
| |
| # Push the remote revision that the manifest is currently pointing to. |
| # |
| # Globals: |
| # DRY_RUN |
| # PUSH_BRANCH_PREFIX |
| # PUSH_SKIP_VALIDATION |
| # REPO_PROJECT |
| # REPO_REMOTE |
| # REPO_UPSTREAM |
| # TARGET_GERRIT_NAME |
| # TRY_QUALIFYING_REFS |
| # Arguments: |
| # None |
| # Returns: |
| # None |
| push_current_upstream_revision() |
| { |
| if [ -z "${REPO_UPSTREAM:-}" ]; then |
| log_e "REPO_UPSTREAM not set in manifest project ${REPO_PROJECT}. Use" \ |
| "a (release) manifest that has 'upstream' attributes defined." |
| fi |
| |
| _push_reference "${REPO_UPSTREAM}" |
| } |
| |
| if [ -n "${DEBUG:-}" ]; then |
| set -x |
| fi |