#!/bin/bash # Copyright 2021 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. set -e readonly GERRIT_URL=https://chromium-review.googlesource.com readonly ORIGIN=https://chromium.googlesource.com/chromiumos/platform/crosvm readonly RETRIES=3 readonly MIN_COMMIT_COUNT=${MIN_COMMIT_COUNT:-5} gerrit_api_get() { # GET request to the gerrit API. Strips XSSI protection line from output. # See: https://gerrit-review.googlesource.com/Documentation/dev-rest-api.html local url="${GERRIT_URL}/${1}" curl --silent "$url" | tail -n +2 } gerrit_api_post() { # POST to gerrit API using http cookies from git. local endpoint=$1 local body=$2 local cookie_file=$(git config http.cookiefile) if [[ -z "${cookie_file}" ]]; then echo 1>&2 "Cannot find git http cookie file." return 1 fi local url="${GERRIT_URL}/${endpoint}" curl --silent \ --cookie "${cookie_file}" \ -X POST \ -d "${body}" \ -H "Content-Type: application/json" \ "$url" | tail -n +2 } query_change() { # Query gerrit for a specific change. # See: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-change gerrit_api_get "changes/$1/?o=CURRENT_REVISION" } query_changes() { # Query gerrit for a list of changes. # See: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes local query=$(printf '%s+' "$@") gerrit_api_get "changes/?q=${query}" } query_related_changes() { # Query related changes from gerrit. # See: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-related-changes gerrit_api_get "changes/$1/revisions/current/related" } get_previous_merge_id() { # Query all open merge commits previously made by crosvm-bot. May be null if # none are open. query=( project:chromiumos/platform/crosvm branch:chromeos status:open owner:crosvm-bot@crosvm-packages.iam.gserviceaccount.com -hashtag:dryrun ) # Pick the one that was created last. query_changes "${query[@]}" | jq --raw-output 'sort_by(.created)[-1].change_id' } get_last_change_in_chain() { # Use the related changes API to find the last change in the chain of # commits. local change_id=$1 # The list of commits is sorted by the git commit order, with the latest # change first and includes the current change. local last_change last_change=$(query_related_changes "$change_id" | jq --raw-output \ "[.changes[] | select(.status == \"NEW\")][0].change_id") # If there are no related changes the list will be empty. if [ "$last_change" == "null" ]; then echo "${change_id}" else echo "${last_change}" fi } fetch_change() { # Fetch the provided change and print the commit sha. local change_id=$1 # Find the git ref we need to fetch. local change_ref change_ref=$(query_change "$change_id" | jq --raw-output -e ".revisions[.current_revision].ref") git fetch -q origin "${change_ref}" } get_dry_run_ids() { # Query all dry run changes. They are identified by the hashtag:dryrun when # uploaded. query=( project:chromiumos/platform/crosvm branch:chromeos status:open hashtag:dryrun owner:crosvm-bot@crosvm-packages.iam.gserviceaccount.com ) query_changes "${query[@]}" | jq --raw-output '.[].change_id' } abandon_dry_runs() { # Abandon all pending dry run commits for change in $(get_dry_run_ids); do echo "Abandoning ${GERRIT_URL}/q/${change}" gerrit_api_post "a/changes/${change}/abandon" "{}" >/dev/null done } gerrit_prerequisites() { # Authenticate to GoB if we don't already have a cookie. # This should only happen when running in Kokoro, not locally. # See: go/gob-gce if [[ -z $(git config http.cookiefile) ]]; then git clone https://gerrit.googlesource.com/gcompute-tools \ "${KOKORO_ARTIFACTS_DIR}/gcompute-tools" "${KOKORO_ARTIFACTS_DIR}/gcompute-tools/git-cookie-authdaemon" --no-fork # Setup correct user info for the service account. git config user.name "Crosvm Bot" git config user.email crosvm-bot@crosvm-packages.iam.gserviceaccount.com fi # We cannot use the original origin that kokoro used, as we no longer have # access the GoB host via rpc://. git remote remove origin git remote add origin ${ORIGIN} git fetch -q origin # Set up gerrit Change-Id hook. mkdir -p .git/hooks curl -Lo .git/hooks/commit-msg \ https://gerrit-review.googlesource.com/tools/hooks/commit-msg chmod +x .git/hooks/commit-msg } upload() { git push origin \ "HEAD:refs/for/chromeos%r=crosvm-uprev@google.com,$1" } upload_with_retries() { # Try uploading to gerrit. Retry due to errors on first upload. # See: b/209031134 for i in $(seq 1 $RETRIES); do echo "Push attempt $i" if upload "$1"; then return 0 fi done return 1 } main() { cd "${KOKORO_ARTIFACTS_DIR}/git/crosvm" gerrit_prerequisites # Make a copy of the merge script, so we are using the HEAD version to # create the merge. cp ./tools/chromeos/create_merge "${KOKORO_ARTIFACTS_DIR}/create_merge" # Clean possible stray files from previous builds. git clean -f -d -x git checkout -f # Parent commit to use for this merge. local parent_commit="origin/chromeos" # Query gerrit to find the latest merge commit and fetch it to be used as # a parent. local previous_merge="$(get_previous_merge_id)" if [ "$previous_merge" != "null" ]; then # The oncall may have uploaded a custom merge or cherry-pick on top # of the detected merge. Find the last changed in that chain. local last_change_in_chain=$(get_last_change_in_chain "${previous_merge}") echo "Found previous merge: ${GERRIT_URL}/q/${previous_merge}" echo "Last change in that chain: ${GERRIT_URL}/q/${last_change_in_chain}" fetch_change "${last_change_in_chain}" parent_commit="FETCH_HEAD" fi local merge_count=$(git log --oneline --decorate=no --no-color \ "${parent_commit}..origin/main" | wc -l) if [ "${merge_count}" -lt "$MIN_COMMIT_COUNT" ]; then echo "Not enough commits to merge. Skipping." return fi echo "Checking out parent: ${parent_commit}" git checkout -b chromeos "${parent_commit}" git branch --set-upstream-to origin/chromeos chromeos "${KOKORO_ARTIFACTS_DIR}/create_merge" "origin/main" upload_with_retries echo "Abandoning previous dry runs" abandon_dry_runs echo "Creating dry run merge" git checkout -b dryrun --track origin/chromeos "${KOKORO_ARTIFACTS_DIR}/create_merge" --dry-run-only "origin/main" upload_with_retries "hashtag=dryrun,l=Commit-Queue+1,l=Bot-Commit+1" } main