1#!/bin/bash 2# Copyright 2021 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5set -e 6 7readonly GERRIT_URL=https://chromium-review.googlesource.com 8readonly ORIGIN=https://chromium.googlesource.com/chromiumos/platform/crosvm 9readonly RETRIES=3 10readonly MIN_COMMIT_COUNT=${MIN_COMMIT_COUNT:-5} 11 12gerrit_api_get() { 13 # GET request to the gerrit API. Strips XSSI protection line from output. 14 # See: https://gerrit-review.googlesource.com/Documentation/dev-rest-api.html 15 local url="${GERRIT_URL}/${1}" 16 curl --silent "$url" | tail -n +2 17} 18 19gerrit_api_post() { 20 # POST to gerrit API using http cookies from git. 21 local endpoint=$1 22 local body=$2 23 local cookie_file=$(git config http.cookiefile) 24 if [[ -z "${cookie_file}" ]]; then 25 echo 1>&2 "Cannot find git http cookie file." 26 return 1 27 fi 28 local url="${GERRIT_URL}/${endpoint}" 29 curl --silent \ 30 --cookie "${cookie_file}" \ 31 -X POST \ 32 -d "${body}" \ 33 -H "Content-Type: application/json" \ 34 "$url" | 35 tail -n +2 36} 37 38query_change() { 39 # Query gerrit for a specific change. 40 # See: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-change 41 gerrit_api_get "changes/$1/?o=CURRENT_REVISION" 42} 43 44query_changes() { 45 # Query gerrit for a list of changes. 46 # See: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes 47 local query=$(printf '%s+' "$@") 48 gerrit_api_get "changes/?q=${query}" 49} 50 51query_related_changes() { 52 # Query related changes from gerrit. 53 # See: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-related-changes 54 gerrit_api_get "changes/$1/revisions/current/related" 55} 56 57get_previous_merge_id() { 58 # Query all open merge commits previously made by crosvm-bot. May be null if 59 # none are open. 60 query=( 61 project:chromiumos/platform/crosvm 62 branch:chromeos 63 status:open 64 owner:crosvm-bot@crosvm-packages.iam.gserviceaccount.com 65 -hashtag:dryrun 66 ) 67 # Pick the one that was created last. 68 query_changes "${query[@]}" | 69 jq --raw-output 'sort_by(.created)[-1].id' 70} 71 72get_last_change_in_chain() { 73 # Use the related changes API to find the last change in the chain of 74 # commits. 75 local change_id=$1 76 77 # The list of commits is sorted by the git commit order, with the latest 78 # change first and includes the current change. 79 local last_change 80 last_change=$(query_related_changes "$change_id" | 81 jq --raw-output "[.changes[] | select(.status == \"NEW\")][0].change_id") 82 83 # If there are no related changes the list will be empty. 84 if [ "$last_change" == "null" ]; then 85 echo "${change_id}" 86 else 87 # The related API does not give us the unique ID of changes. Build it manually. 88 echo "chromiumos%2Fplatform%2Fcrosvm~chromeos~${last_change}" 89 fi 90} 91 92fetch_change() { 93 # Fetch the provided change and print the commit sha. 94 local change_id=$1 95 96 # Find the git ref we need to fetch. 97 local change_ref 98 change_ref=$(query_change "$change_id" | 99 jq --raw-output -e ".revisions[.current_revision].ref") 100 git fetch -q origin "${change_ref}" 101} 102 103get_dry_run_ids() { 104 # Query all dry run changes. They are identified by the hashtag:dryrun when 105 # uploaded. 106 query=( 107 project:chromiumos/platform/crosvm 108 branch:chromeos 109 status:open 110 hashtag:dryrun 111 owner:crosvm-bot@crosvm-packages.iam.gserviceaccount.com 112 ) 113 query_changes "${query[@]}" | 114 jq --raw-output '.[].id' 115} 116 117abandon_dry_runs() { 118 # Abandon all pending dry run commits 119 for change in $(get_dry_run_ids); do 120 echo "Abandoning ${GERRIT_URL}/q/${change}" 121 gerrit_api_post "a/changes/${change}/abandon" "{}" >/dev/null 122 done 123} 124 125gerrit_prerequisites() { 126 # Authenticate to GoB if we don't already have a cookie. 127 # This should only happen when running in Kokoro, not locally. 128 # See: go/gob-gce 129 if [[ -z $(git config http.cookiefile) ]]; then 130 git clone https://gerrit.googlesource.com/gcompute-tools \ 131 "${KOKORO_ARTIFACTS_DIR}/gcompute-tools" 132 "${KOKORO_ARTIFACTS_DIR}/gcompute-tools/git-cookie-authdaemon" --no-fork 133 134 # Setup correct user info for the service account. 135 git config user.name "Crosvm Bot" 136 git config user.email crosvm-bot@crosvm-packages.iam.gserviceaccount.com 137 fi 138 139 # We cannot use the original origin that kokoro used, as we no longer have 140 # access the GoB host via rpc://. 141 git remote remove origin 142 git remote add origin ${ORIGIN} 143 git fetch -q origin 144 145 # Set up gerrit Change-Id hook. 146 mkdir -p .git/hooks 147 curl -Lo .git/hooks/commit-msg \ 148 https://gerrit-review.googlesource.com/tools/hooks/commit-msg 149 chmod +x .git/hooks/commit-msg 150} 151 152upload() { 153 git push origin \ 154 "HEAD:refs/for/chromeos%r=crosvm-uprev@google.com,$1" 155} 156 157upload_with_retries() { 158 # Try uploading to gerrit. Retry due to errors on first upload. 159 # See: b/209031134 160 for i in $(seq 1 $RETRIES); do 161 echo "Push attempt $i" 162 if upload "$1"; then 163 return 0 164 fi 165 done 166 return 1 167} 168 169main() { 170 cd "${KOKORO_ARTIFACTS_DIR}/git/crosvm" 171 172 gerrit_prerequisites 173 174 # Make a copy of the merge script, so we are using the HEAD version to 175 # create the merge. 176 cp ./tools/chromeos/create_merge "${KOKORO_ARTIFACTS_DIR}/create_merge" 177 178 # Clean possible stray files from previous builds. 179 git clean -f -d -x 180 git checkout -f 181 182 # Parent commit to use for this merge. 183 local parent_commit="origin/chromeos" 184 185 # Query gerrit to find the latest merge commit and fetch it to be used as 186 # a parent. 187 local previous_merge="$(get_previous_merge_id)" 188 if [ "$previous_merge" != "null" ]; then 189 # The oncall may have uploaded a custom merge or cherry-pick on top 190 # of the detected merge. Find the last changed in that chain. 191 local last_change_in_chain=$(get_last_change_in_chain "${previous_merge}") 192 echo "Found previous merge: ${GERRIT_URL}/q/${previous_merge}" 193 echo "Last change in that chain: ${GERRIT_URL}/q/${last_change_in_chain}" 194 fetch_change "${last_change_in_chain}" 195 parent_commit="FETCH_HEAD" 196 fi 197 198 echo "Checking out parent: ${parent_commit}" 199 git checkout -b chromeos "${parent_commit}" 200 git branch --set-upstream-to origin/chromeos chromeos 201 202 local merge_count=$(git log --oneline --decorate=no --no-color \ 203 "${parent_commit}..origin/main" | wc -l) 204 if [ "${merge_count}" -ge "$MIN_COMMIT_COUNT" ]; then 205 "${KOKORO_ARTIFACTS_DIR}/create_merge" "origin/main" 206 else 207 echo "Not enough commits to merge." 208 fi 209 210 upload_with_retries 211 212 echo "Abandoning previous dry runs" 213 abandon_dry_runs 214 215 echo "Creating dry run merge" 216 git checkout -b dryrun --track origin/chromeos 217 218 "${KOKORO_ARTIFACTS_DIR}/create_merge" --dry-run-only "origin/main" 219 upload_with_retries "hashtag=dryrun,l=Commit-Queue+1,l=Bot-Commit+1" 220} 221 222main 223