Skip to content
Snippets Groups Projects
update-production 4.17 KiB
Newer Older
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p git curl python3

set -eux -o pipefail

main() {
    local TOKEN=$1
    shift
    local SERVER_URL=$1
    shift
    local PROJECT_ID=$1
    shift
    local SOURCE_BRANCH=$1
    shift
    local TARGET_BRANCH=$1
    shift

    # Make sure the things we want to talk about are locally known.  GitLab
    # seems to prefer to know about as few refs as possible.
    checkout_git_ref "$SOURCE_BRANCH"
    checkout_git_ref "$TARGET_BRANCH"

    # If there have been no changes we'll just abandon this update.
    if ! ensure_changes "$SOURCE_BRANCH" "$TARGET_BRANCH"; then
	echo "No changes."
	exit 0
    fi
    local NOTES=$(describe_update "$SOURCE_BRANCH" "$TARGET_BRANCH")

    create_merge_request "$TOKEN" "$SERVER_URL" "$PROJECT_ID" "$SOURCE_BRANCH" "$TARGET_BRANCH" "$NOTES"
}

checkout_git_ref() {
    local REF=$1
    shift

    git fetch origin "$REF"
}

ensure_changes() {
    local SOURCE_BRANCH=$1
    shift
    local TARGET_BRANCH=$1
    shift

    if [ "$(git rev-parse origin/"$SOURCE_BRANCH")" = "$(git rev-parse origin/"$TARGET_BRANCH")" ]; then
describe_merge_request() {
    git show $rev | grep 'See merge request' | sed -e 's/See merge request //' | tr -d '[:space:]'
}

describe_merge_requests() {
    local RANGE=$1
    shift
    local TARGET=$1
    shift

    # Find all of the relevant merge revisions
    local onelines=$(git log --merges --first-parent -m --oneline "$RANGE" | grep "into '$TARGET'")

    # Describe each merge revision
    local IFS=$'\n'
    for line in $onelines; do
	local rev=$(echo "$line" | cut -d ' ' -f 1)
	echo -n "* "
	describe_merge_request $rev
	echo
    done
}

    local SOURCE_BRANCH=$1
    shift
    local TARGET_BRANCH=$1
    shift

    # Since production production (target) should not diverge from develop
    # (source) it is fine to use `..` instead of `...` in the git ranges here.
    # `...` encounters problems related to discovering the merge base because
    # of the way GitLab manages the git checkout on CI (I think).

    local NOTES=$(git diff origin/"$TARGET_BRANCH"..origin/"$SOURCE_BRANCH" -- DEPLOYMENT-NOTES.rst)

    # There often are no notes and that makes for boring reading so toss in a
    # diffstat as well.
    local DIFFSTAT=$(git diff --stat origin/"$TARGET_BRANCH"..origin/"$SOURCE_BRANCH")

    local WHEN=$(git log --max-count=1 --format='%cI' origin/"$TARGET_BRANCH")

    # Describe all of the MRs that were merged into the source branch that are
    # about to be merged into the target branch.
    local MR=$(describe_merge_requests origin/"$TARGET_BRANCH"..origin/"$SOURCE_BRANCH" "$SOURCE_BRANCH")
    echo "\
Changes from $SOURCE_BRANCH since $WHEN
=======================================

Deployment Notes
----------------
\`\`\`
$NOTES
\`\`\`

Included Merge Requests
-----------------------
$MR

Diff Stat
---------
\`\`\`
$DIFFSTAT
\`\`\`
"
}

create_merge_request() {
    local TOKEN=$1
    shift
    local SERVER_URL=$1
    shift
    local PROJECT_ID=$1
    shift
    # THe source branch of the MR.
    local SOURCE_BRANCH=$1
    shift
    # The target branch of the MR.
    local TARGET_BRANCH=$1
    shift
    local NOTES=$1
    shift

    local BODY=$(python3 -c '
import sys, json
print(json.dumps({
    "id": sys.argv[1],
    "source_branch": sys.argv[2],
    "target_branch": sys.argv[3],
    "remove_source_branch": True,
    "title": f"update {sys.argv[3]}",
    "description": sys.argv[4],
}))
' "$PROJECT_ID" "$SOURCE_BRANCH" "$TARGET_BRANCH" "$NOTES")

    curl --verbose -X POST --data "${BODY}" --header "Content-Type: application/json" --header "PRIVATE-TOKEN: ${TOKEN}" "${SERVER_URL}/api/v4/projects/${PROJECT_ID}/merge_requests"
}

# Pull the GitLab token from the environment here so we can work with them as
# arguments everywhere else.  They're passed to us in the environment because
# *maybe* this is *slightly* safer than passing them in argv.
#
# The name is slightly weird because it is shared with the update-nixpkgs job.
TOKEN="$UPDATE_NIXPKGS_PRIVATE_TOKEN"

# Before proceeding, remove the secrets from our environment so we don't pass
# them to child processes - none of which need them.
unset UPDATE_NIXPKGS_PRIVATE_TOKEN