• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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