Guenter Roeck | 9aa6957 | 2017-12-12 13:36:30 -0800 | [diff] [blame] | 1 | #!/bin/bash |
| 2 | # |
| 3 | # Copyright 2018 The Chromium OS Authors. All rights reserved. |
| 4 | # Use of this source code is governed by a BSD-style license that can be |
| 5 | # found in the LICENSE file. |
| 6 | # |
| 7 | # Script to merge upstream tags into chromeos. |
| 8 | # The command creates a new branch with merge results. |
| 9 | # If necessary, it also pushes the tag into the remote |
| 10 | # repository and creates a branch pointing to it. |
| 11 | |
| 12 | readonly notify_to="chromeos-kernel@google.com" |
| 13 | readonly notify_cc="chromium-os-reviews@chromium.org" |
| 14 | |
| 15 | # Valid tag pattern. |
| 16 | PATTERN="^v[2-9](\.[0-9]+)+$" |
| 17 | PATTERN_RC="^v[2-9](\.[0-9]+)+-rc$" |
| 18 | GLOB="v[2-9].*[0-9]" |
| 19 | NUMBER="^[1-9][0-9]*$" |
| 20 | |
| 21 | if git help push | grep -q push-option; then |
| 22 | git_skip_validation="-o skip-validation" |
| 23 | fi |
| 24 | |
| 25 | # Initial parameter values. |
| 26 | changeid="" # Get Change-Id from CL or generate new Change-Id. |
| 27 | bug="" # Get Bug-Id from CL or provide on on command line. |
| 28 | tag="" # Tag to merge; must be provided on command line. |
| 29 | force=0 # Do not override Change-Id / Bug-Id. |
| 30 | prepare=0 # Do not prepare for upload. |
| 31 | upload=0 # Do not upload into Gerrit. |
| 32 | do_dryrun=0 # If 1, don't push anything upstream, don't send email. |
| 33 | notify=0 # Do not send notification e-mail. |
| 34 | deadline=3 # Feedback deadline (in days, default 3). |
| 35 | changes=() # List of uncommitted CLs to be applied prior to merge. |
| 36 | patches=() # List of patches to apply before committing merge. |
Guenter Roeck | c98bb9a | 2019-08-02 12:57:46 -0700 | [diff] [blame^] | 37 | reverts=() # List of patches to revert as part of merge, after merge |
| 38 | prereverts=() # List of patches to revert as part of merge, prior to merge |
Guenter Roeck | 9aa6957 | 2017-12-12 13:36:30 -0800 | [diff] [blame] | 39 | dependency="" # No dependency |
| 40 | Subject="" # default subject |
| 41 | |
| 42 | # derived parameters |
| 43 | skip_merge=0 # Skip actual merge and upload. |
| 44 | # Will be set if tag has already been merged and force is true. |
| 45 | |
| 46 | readonly tmpfile=$(mktemp) |
| 47 | |
| 48 | trap 'rm -f "${tmpfile}"' EXIT |
| 49 | trap 'exit 2' SIGHUP SIGINT SIGQUIT SIGTERM |
| 50 | |
| 51 | error() { |
| 52 | printf '%b: error: %b\n' "${0##*/}" "$*" >&2 |
| 53 | } |
| 54 | |
| 55 | die() { |
| 56 | error "$@" |
| 57 | exit 1 |
| 58 | } |
| 59 | |
| 60 | usage() { |
| 61 | cat <<-EOF |
| 62 | Usage: ${0##*/} [options] tag |
| 63 | |
| 64 | Parameters: |
| 65 | tag Tag, branch, or SHA to merge. Must be either a valid stable |
| 66 | branch release tag, a valid branch name, or a valid SHA. |
| 67 | |
| 68 | Options: |
| 69 | -b bug-id[,bug-id] ... |
William K Lin | 6168372 | 2021-03-22 06:07:53 +0000 | [diff] [blame] | 70 | Bug-id or list of bug IDs. Must be valid buganizer |
Guenter Roeck | 9aa6957 | 2017-12-12 13:36:30 -0800 | [diff] [blame] | 71 | bug ID. Mandatory unless the merge branch already exists |
| 72 | locally or in Gerrit. |
| 73 | -c change-id Change-Id as used by Gerrit. Optional. |
| 74 | -d deadline Feedback deadline in days (default: ${deadline}) |
| 75 | -f Force. Override existing Change-Id and bug number. |
| 76 | -h Display help text and exit. |
| 77 | -l change-id Apply patch extracted from CL:change-id prior to merge. |
| 78 | May be repeated multiple times. |
| 79 | -n Send notification e-mail to ${notify_to}. |
Guenter Roeck | 33f263a | 2019-06-28 08:51:23 -0700 | [diff] [blame] | 80 | -q dependency Add dependency (Cq-Depend: <dependency>) |
Guenter Roeck | 9aa6957 | 2017-12-12 13:36:30 -0800 | [diff] [blame] | 81 | -p Prepare for upload into Gerrit. Implied if -u is specified. |
| 82 | -r Name of branch to base merge on. Determined from stable |
| 83 | release tag or from target branch name if not provided. |
| 84 | Must be existing local branch. Will be pushed into gerrit |
| 85 | as part of the merge process if not already available in |
| 86 | gerrit, and has to follow gerrit commit rules. |
Guenter Roeck | c98bb9a | 2019-08-02 12:57:46 -0700 | [diff] [blame^] | 87 | -P sha Revert patch <sha> as part of merge, pre-merge |
| 88 | May be repeated multiple times. |
| 89 | -R sha Revert patch <sha> as part of merge, post-merge |
| 90 | May be repeated multiple times. |
Guenter Roeck | 9aa6957 | 2017-12-12 13:36:30 -0800 | [diff] [blame] | 91 | -s Simulate, or dry-run. Don't actually push anything into |
| 92 | gerrit, and don't send e-mails. |
| 93 | -S subject Replace default subject line with provided string |
| 94 | -t Target branch name. The branch must exist in the Chrome OS |
| 95 | repository. |
| 96 | -u Upload merge into Gerrit. |
Guenter Roeck | 7acc7c1 | 2019-08-02 12:28:37 -0700 | [diff] [blame] | 97 | -x patchfile Patch to apply before committing merge. Patch will be applied |
| 98 | with "patch -p 1 < patchfile". May be repeated several times. |
Guenter Roeck | 9aa6957 | 2017-12-12 13:36:30 -0800 | [diff] [blame] | 99 | EOF |
| 100 | |
| 101 | if [[ $# -gt 0 ]]; then |
| 102 | echo |
| 103 | die "$@" |
| 104 | fi |
| 105 | exit 0 |
| 106 | } |
| 107 | |
| 108 | # Find and report remote. |
| 109 | find_remote() { |
| 110 | local url="$1" |
| 111 | local remote |
| 112 | |
| 113 | for remote in $(git remote 2>/dev/null); do |
| 114 | rurl=$(git remote get-url "${remote}") |
| 115 | # ignore trailing '/' when comparing repositories |
| 116 | if [[ "${rurl%/}" == "${url%/}" ]]; then |
| 117 | echo "${remote}" |
| 118 | break |
| 119 | fi |
| 120 | done |
| 121 | } |
| 122 | |
| 123 | # Find remote. If there is no remote pointing to the referenced |
| 124 | # kernel repository, create one. |
| 125 | find_create_remote() { |
| 126 | local url="$1" |
| 127 | local default="$2" |
| 128 | local result |
| 129 | |
| 130 | result="$(find_remote "${url}")" |
| 131 | if [[ -z "${result}" ]]; then |
| 132 | git remote add "${default}" "${url}" |
| 133 | result="${default}" |
| 134 | fi |
| 135 | |
| 136 | echo "${result}" |
| 137 | } |
| 138 | |
| 139 | # Find and report CrOS remote. |
| 140 | # This is useful if the command runs on a checked out |
| 141 | # tree with several remotes. |
| 142 | find_chromeos() { |
| 143 | local url="https://chromium.googlesource.com/chromiumos/third_party/kernel" |
| 144 | |
| 145 | find_remote "${url}" |
| 146 | } |
| 147 | |
| 148 | # Find stable remote. If there is no remote pointing to the stable |
| 149 | # kernel repository, create one. |
| 150 | find_stable() { |
| 151 | local url="git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git" |
| 152 | |
| 153 | find_create_remote "${url}" "stable" |
| 154 | } |
| 155 | |
| 156 | # Find stable remote. If there is no remote pointing to the stable |
| 157 | # kernel repository, create one. |
| 158 | find_stable_rc() { |
| 159 | local url="git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable-rc.git" |
| 160 | |
| 161 | find_create_remote "${url}" "stable-rc" |
| 162 | } |
| 163 | |
| 164 | do_getparams() { |
| 165 | local bugs="" # List of bugs |
| 166 | local nbug="" # Numerical part of bug #, for validation. |
| 167 | local _bug |
| 168 | local option |
| 169 | local vtag |
| 170 | |
Guenter Roeck | c98bb9a | 2019-08-02 12:57:46 -0700 | [diff] [blame^] | 171 | while getopts "b:c:d:fhl:nP:pq:r:R:st:uS:x:" option; do |
Guenter Roeck | 9aa6957 | 2017-12-12 13:36:30 -0800 | [diff] [blame] | 172 | case ${option} in |
| 173 | b) bugs="${OPTARG}" ;; |
| 174 | c) changeid="Change-Id: ${OPTARG}" ;; |
| 175 | d) deadline="${OPTARG}" |
| 176 | if ! [[ "${deadline}" =~ ${NUMBER} ]]; then |
| 177 | die "Deadline must be numeric value > 0 (${deadline})" |
| 178 | fi |
| 179 | ;; |
| 180 | f) force=1 ;; |
| 181 | l) changes+=("${OPTARG}") ;; |
| 182 | n) notify=1 ;; |
| 183 | p) prepare=1 ;; |
| 184 | q) dependency="${OPTARG}" ;; |
| 185 | r) rbranch="${OPTARG}" ;; |
Guenter Roeck | c98bb9a | 2019-08-02 12:57:46 -0700 | [diff] [blame^] | 186 | P) prereverts+=("${OPTARG}") ;; |
| 187 | R) reverts+=("${OPTARG}") ;; |
Guenter Roeck | 9aa6957 | 2017-12-12 13:36:30 -0800 | [diff] [blame] | 188 | t) tbranch="${OPTARG}" ;; |
| 189 | s) do_dryrun=1 ;; |
| 190 | S) Subject="${OPTARG}" ;; |
| 191 | u) upload=1 prepare=1 ;; |
| 192 | x) patches+=("${OPTARG}") ;; |
| 193 | h|?|*) usage ;; |
| 194 | esac |
| 195 | done |
| 196 | shift $((OPTIND - 1)) |
| 197 | tag=$1 |
| 198 | if [[ -z "${tag}" ]]; then |
| 199 | usage "tag parameter is mandatory" |
| 200 | fi |
| 201 | vtag=$(echo "${tag}" | grep -E "${PATTERN}") |
| 202 | if [[ "${tag}" != "${vtag}" ]]; then |
| 203 | # Not a stable release tag, meaning we can not get it from a stable release. |
| 204 | # Maybe it is a stable release candidate. |
| 205 | vtag=$(echo "${tag}" | grep -E "${PATTERN_RC}") |
| 206 | if [[ "${tag}" != "${vtag}" ]]; then |
| 207 | # Make sure that the reference exists and bail out if not. |
| 208 | if ! git rev-parse --verify "${tag}" >/dev/null 2>&1; then |
| 209 | die "Unknown reference '${tag}'." |
| 210 | fi |
| 211 | else |
| 212 | die "${tag} references a stable release candidate. Not supported yet." |
| 213 | fi |
| 214 | fi |
| 215 | if [[ -n "${rbranch}" ]]; then |
| 216 | if ! git rev-parse --verify "${rbranch}" >/dev/null 2>&1; then |
| 217 | die "No such branch: ${rbranch}." |
| 218 | fi |
| 219 | fi |
William K Lin | 6168372 | 2021-03-22 06:07:53 +0000 | [diff] [blame] | 220 | |
Guenter Roeck | 9aa6957 | 2017-12-12 13:36:30 -0800 | [diff] [blame] | 221 | if [[ -n "${bugs}" ]]; then |
| 222 | for _bug in ${bugs//,/ }; do |
| 223 | if [[ "${_bug}" == b:* ]]; then # buganizer |
| 224 | nbug="${_bug##b:}" |
| 225 | elif [[ "${_bug}" == b/* ]]; then # buganizer, alternative |
| 226 | nbug="${_bug##b/}" |
William K Lin | 6168372 | 2021-03-22 06:07:53 +0000 | [diff] [blame] | 227 | else # crbug etc are not allowed. |
| 228 | die "Invalid bug ID '${_bug}'." |
Guenter Roeck | 9aa6957 | 2017-12-12 13:36:30 -0800 | [diff] [blame] | 229 | fi |
| 230 | if [[ ! "${nbug}" =~ ${NUMBER} ]]; then |
| 231 | die "Invalid bug ID '${_bug}'." |
| 232 | fi |
| 233 | done |
| 234 | bug="BUG=${bugs}" |
| 235 | fi |
Guenter Roeck | 33f263a | 2019-06-28 08:51:23 -0700 | [diff] [blame] | 236 | dependency="${dependency:+Cq-Depend: ${dependency}}" |
Guenter Roeck | 9aa6957 | 2017-12-12 13:36:30 -0800 | [diff] [blame] | 237 | } |
| 238 | |
| 239 | # Validate environment and repository. |
| 240 | # We need a couple of commands, the repository must be |
| 241 | # a CrOS kernel repository, and it must be clean. |
| 242 | do_validate() { |
| 243 | local gerrit |
| 244 | local chromeos |
| 245 | local jq |
| 246 | |
| 247 | gerrit=$(which gerrit) |
| 248 | if [[ -z "${gerrit}" ]]; then |
| 249 | die "gerrit is required. Get from chromite or run from chroot." |
| 250 | fi |
| 251 | jq=$(which jq) |
| 252 | if [[ -z ${jq} ]]; then |
| 253 | die "jq is required. Install (apt-get install jq) or run from chroot." |
| 254 | fi |
| 255 | chromeos=$(find_chromeos) |
| 256 | if [[ -z ${chromeos} ]]; then |
| 257 | die "$(pwd) is not a Chromium OS kernel repository." |
| 258 | fi |
| 259 | if [[ -n "$(git status -s)" ]]; then |
| 260 | die "Requires clean repository." |
| 261 | fi |
| 262 | if [[ -n "${tbranch}" ]]; then |
| 263 | if ! git rev-parse --verify "${chromeos}/${tbranch}" >/dev/null 2>&1; then |
| 264 | die "No such branch: ${chromeos}/${tbranch}." |
| 265 | fi |
| 266 | fi |
| 267 | } |
| 268 | |
| 269 | # Validate provided Change-IDs. |
| 270 | do_validate_changeids() { |
| 271 | local cl |
| 272 | local ref |
| 273 | |
| 274 | for cl in "${changes[@]}"; do |
| 275 | ref=$(gerrit --json search "change:${cl}" \ |
| 276 | | jq ".[].currentPatchSet.ref") |
| 277 | if [[ -z "${ref}" ]]; then |
| 278 | die "No such Change-Id: ${cl}." |
| 279 | fi |
| 280 | done |
| 281 | } |
| 282 | |
| 283 | # Initialize global variables, plus some more validation. |
| 284 | do_setup() { |
| 285 | readonly stable=$(find_stable) |
| 286 | readonly stable_rc=$(find_stable_rc) |
| 287 | local vtag |
| 288 | local dvtag |
| 289 | |
| 290 | # If a stable release tag is provided, we need to update stable |
| 291 | # at this point to get the tag if it is not already available. |
| 292 | vtag=$(echo "${tag}" | grep -E "${PATTERN}") |
| 293 | if [[ "${tag}" == "${vtag}" ]]; then |
| 294 | if ! git rev-parse --verify "${tag}" >/dev/null 2>&1; then |
| 295 | if ! git fetch "${stable}" > /dev/null 2>&1; then |
| 296 | die "Failed to update stable release." |
| 297 | fi |
| 298 | if ! git rev-parse --verify "${tag}" >/dev/null 2>&1; then |
| 299 | die "Reference ${tag} not available." |
| 300 | fi |
| 301 | fi |
| 302 | else |
| 303 | # This might be a stable release candidate. |
| 304 | vtag=$(echo "${tag}" | grep -E "${PATTERN_RC}") |
| 305 | if [[ "${tag}" == "${vtag}" ]]; then |
| 306 | git fetch "${stable_rc}" > /dev/null 2>&1 |
| 307 | # The stable release tag is "vX.Y.Z-rc". Stable release candidate |
| 308 | # branches are named "remote/linux-X.Y.y". |
| 309 | # Extract 'X' and 'Y', create the remote branch name, |
| 310 | # clone/update the remote branch, and set a matching tag |
| 311 | # on top of it. |
| 312 | |
| 313 | die "Stable release candidates are not yet supported." |
| 314 | fi |
| 315 | fi |
| 316 | |
| 317 | readonly ctag=$(git describe --match "${GLOB}" --abbrev=0 "${tag}" \ |
| 318 | 2>/dev/null | cut -f1,2 -d. | sed -e 's/v//') |
| 319 | readonly dtag=$(git describe --tags "${tag}") |
| 320 | |
| 321 | # While we accept any valid reference as <tag>, we want it to be based |
| 322 | # on an existing release tag. |
| 323 | dbtag=${dtag%%-*} |
| 324 | dvtag=$(git describe --tags --abbrev=0 "${dtag}") |
| 325 | if [[ "${dbtag}" != "${dvtag}" ]]; then |
| 326 | die "${tag} (${dtag}) is not based on an existing release tag." |
| 327 | fi |
| 328 | |
| 329 | readonly chromeos=$(find_chromeos) |
| 330 | if [[ -z "${chromeos}" ]]; then |
| 331 | die "Chromium OS kernel repository not found." |
| 332 | fi |
| 333 | |
| 334 | # cbranch: Chromeos branch name |
| 335 | # mcbranch: local copy (baseline) |
| 336 | # ocbranch: remote (target) branch |
| 337 | # |
| 338 | # Note: This assumes that the target repository is ${chromeos}, |
| 339 | # even if a remote branch has been specified. It might make sense |
| 340 | # to make this configurable. |
| 341 | if [[ -n "${tbranch}" ]]; then |
| 342 | readonly cbranch="${tbranch}" |
| 343 | else |
| 344 | readonly cbranch="chromeos-${ctag}" |
| 345 | fi |
| 346 | if [[ -n "${rbranch}" ]]; then |
| 347 | readonly ocbranch="${rbranch}" |
| 348 | else |
| 349 | readonly ocbranch="${chromeos}/${cbranch}" |
| 350 | fi |
| 351 | |
| 352 | readonly mcbranch="merge/${cbranch}" |
| 353 | |
| 354 | # Topic to use. |
| 355 | readonly topic="merge-${dtag}" |
| 356 | |
| 357 | if ! git rev-parse --verify "${ocbranch}" >/dev/null 2>&1; then |
| 358 | usage "Invalid tag '${tag}': No such branch: '${ocbranch}'" |
| 359 | fi |
| 360 | |
| 361 | # mbranch: Local branch used to execute the merge. |
| 362 | readonly mbranch="${mcbranch}-${dtag}" |
| 363 | |
| 364 | # obranch: chromeos branch used as reference. |
| 365 | # May include local reverts from merge if necessary. |
| 366 | # If necessary, a branch with this name will be created locally and |
| 367 | # in the chromeos repository. It is necessary to perform the merge. |
| 368 | readonly obranch="stable-merge/linux/${dtag}" |
| 369 | |
| 370 | if [[ ${do_dryrun} -ne 0 ]]; then |
| 371 | readonly dryrun="--dry-run" |
| 372 | fi |
| 373 | |
Guenter Roeck | 7acc7c1 | 2019-08-02 12:28:37 -0700 | [diff] [blame] | 374 | Subject="CHROMIUM: ${Subject:-Merge '${tag}' into ${cbranch}}" |
Guenter Roeck | 9aa6957 | 2017-12-12 13:36:30 -0800 | [diff] [blame] | 375 | } |
| 376 | |
| 377 | have_version() { |
| 378 | local tag |
| 379 | local tot_tag |
| 380 | local index |
| 381 | local v1 |
| 382 | local v2 |
| 383 | local vtag |
| 384 | |
| 385 | tag=$1 |
| 386 | vtag=$(echo "${tag}" | grep -E "${PATTERN}") |
| 387 | if [[ "${tag}" != "${vtag}" ]]; then |
| 388 | # Not a release tag, can not evaluate. |
| 389 | return 0 |
| 390 | fi |
| 391 | |
| 392 | tot_tag=$(git describe --match "v[2-9].*[0-9]" --abbrev=0 "${ocbranch}") |
| 393 | |
| 394 | index=1 |
| 395 | while true; do |
| 396 | v1=$(echo "${tag}" | cut -f${index} -d. | sed -e 's/[^0-9]//g') |
| 397 | v2=$(echo "${tot_tag}" | cut -f${index} -d. | sed -e 's/[^0-9]//g') |
| 398 | # If both version numbers are empty, we reached the end of the |
| 399 | # version number string, and the versions are equal. |
| 400 | # Return true. |
| 401 | if [[ -z "${v1}" && -z "${v2}" ]]; then |
| 402 | return 1 |
| 403 | fi |
| 404 | # Interpret empty minor version numbers as version 0. |
| 405 | if [[ -z "${v1}" ]]; then |
| 406 | v1=0 |
| 407 | fi |
| 408 | if [[ -z "${v2}" ]]; then |
| 409 | v2=0 |
| 410 | fi |
| 411 | # If ToT version is larger than tag, return true. |
| 412 | if [[ ${v2} -gt ${v1} ]]; then |
| 413 | return 1 |
| 414 | fi |
| 415 | # If tag version is targer than ToT, return false. |
| 416 | if [[ ${v2} -lt ${v1} ]]; then |
| 417 | return 0 |
| 418 | fi |
| 419 | index=$((index + 1)) |
| 420 | done |
| 421 | } |
| 422 | |
| 423 | # Remove double quotes from beginning and end of a string, and |
| 424 | # remove the escape character from double quotes within the string. |
| 425 | dequote() { |
| 426 | local tmp="${1#\"}" # beginning |
| 427 | tmp="${tmp%\"}" # end |
| 428 | echo "${tmp//\\\"/\"}" # remove embedded escape characters |
| 429 | } |
| 430 | |
| 431 | # Try to find the merge CL. |
| 432 | # Walk through all CLs tagged with the merge topic |
| 433 | # and try to find one with the expected subject line. |
| 434 | # If found, set merge_cl to the respective value for later use. |
| 435 | find_merge_cl() { |
| 436 | local cls |
| 437 | local cl |
| 438 | local subject |
| 439 | |
| 440 | cls=($(gerrit --json search "hashtag:${topic}" \ |
| 441 | | jq ".[].number" | sed -e 's/"//g')) |
| 442 | |
| 443 | for cl in "${cls[@]}"; do |
| 444 | subject=$(dequote "$(gerrit --json search "change:${cl}" \ |
| 445 | | jq ".[].subject")") |
| 446 | if [[ "${subject}" == "${Subject}" ]]; then |
| 447 | merge_cl="${cl}" |
| 448 | break |
| 449 | fi |
| 450 | done |
| 451 | } |
| 452 | |
| 453 | # Prepare for merge. |
| 454 | # - Update remotes. |
| 455 | # - Verify that tag exists. |
| 456 | # - Search for merge in gerrit. If it exists, validate bug ID and Change-Id. |
| 457 | # - Push tag and reference branch into CrOS repository if necessary. |
| 458 | do_prepare() { |
| 459 | local vtag |
| 460 | local obug |
| 461 | local ochangeid |
| 462 | local odependency |
| 463 | local ref |
| 464 | |
| 465 | find_merge_cl |
| 466 | |
| 467 | printf "Updating ${chromeos}..." |
| 468 | git fetch "${chromeos}" > /dev/null |
| 469 | printf "\nUpdating ${mcbranch} ..." |
| 470 | if git rev-parse --verify "${mcbranch}" >/dev/null 2>&1; then |
| 471 | if ! git checkout "${mcbranch}" >/dev/null 2>&1; then |
| 472 | die "Failed to check out '${mcbranch}'." |
| 473 | fi |
| 474 | git pull >/dev/null |
| 475 | else |
| 476 | if ! git checkout -b "${mcbranch}" "${ocbranch}"; then |
| 477 | die "Failed to create '${mcbranch}' from '${ocbranch}'." |
| 478 | fi |
| 479 | fi |
| 480 | echo |
| 481 | |
| 482 | # Abort if chromeos already includes the tag unless 'force' is set. |
| 483 | if ! have_version "${dtag}"; then |
| 484 | if [[ ${force} -eq 0 ]]; then |
| 485 | die "Tag or reference '${tag}' already in '${ocbranch}'." |
| 486 | fi |
| 487 | echo "Warning: Tag '${tag}' already in '${ocbranch}'." |
| 488 | echo "Will not merge/notify/prepare/upload." |
| 489 | skip_merge=1 |
| 490 | prepare=0 |
| 491 | notify=0 |
| 492 | upload=0 |
| 493 | fi |
| 494 | |
| 495 | if [[ -n "${merge_cl}" ]]; then |
| 496 | ref=$(dequote "$(gerrit --json search "change:${merge_cl}" \ |
| 497 | | jq ".[].currentPatchSet.ref")") |
| 498 | fi |
| 499 | if [[ -n "${ref}" ]]; then |
| 500 | if ! git fetch "${chromeos}" "${ref}" >/dev/null 2>&1; then |
| 501 | die "Failed to fetch '${ref}' from '${chromeos}'." |
| 502 | fi |
| 503 | git show -s --format=%B FETCH_HEAD > "${tmpfile}" |
| 504 | else |
| 505 | # We may have a local merge branch. |
| 506 | if git rev-parse --verify "${mbranch}" >/dev/null 2>&1; then |
| 507 | local subject |
| 508 | |
| 509 | # Make sure the branch actually includes the merge we are looking for. |
| 510 | git show -s --format=%B "${mbranch}" > "${tmpfile}" |
| 511 | subject="$(head -n 1 "${tmpfile}")" |
| 512 | if [[ "${subject}" != "${Subject}" ]]; then |
| 513 | rm -f "${tmpfile}" |
| 514 | touch "${tmpfile}" |
| 515 | fi |
| 516 | else |
| 517 | rm -f "${tmpfile}" |
| 518 | touch "${tmpfile}" |
| 519 | fi |
| 520 | fi |
| 521 | obug=$(grep "^BUG=" "${tmpfile}") |
| 522 | if [[ -n "${bug}" && -n "${obug}" && "${bug}" != "${obug}" \ |
| 523 | && ${force} -eq 0 ]]; then |
| 524 | die "Bug mismatch: '${bug}' <-> '${obug}'. Use -f to override." |
| 525 | fi |
| 526 | if [[ -z "${bug}" ]]; then |
| 527 | bug="${obug}" |
| 528 | fi |
| 529 | if [[ -z "${bug}" ]]; then |
| 530 | die "New merge: must specify bug ID." |
| 531 | fi |
| 532 | ochangeid=$(grep "^Change-Id:" "${tmpfile}") |
| 533 | if [[ -n "${changeid}" && -n "${ochangeid}" \ |
| 534 | && "${changeid}" != "${ochangeid}" && ${force} -eq 0 ]]; then |
| 535 | die "Change-Id mismatch: '${changeid}' <-> '${ochangeid}'. Use -f to override." |
| 536 | fi |
| 537 | if [[ -z "${changeid}" ]]; then |
| 538 | changeid="${ochangeid}" |
| 539 | fi |
| 540 | |
Guenter Roeck | 33f263a | 2019-06-28 08:51:23 -0700 | [diff] [blame] | 541 | odependency=$(grep "^Cq-Depend:" "${tmpfile}") |
Guenter Roeck | 9aa6957 | 2017-12-12 13:36:30 -0800 | [diff] [blame] | 542 | if [[ -n "${dependency}" && -n "${odependency}" && \ |
| 543 | "${dependency}" != "${odependency}" && ${force} -eq 0 ]]; then |
| 544 | die "Dependency mismatch: '${dependency}' <-> '${odependency}'. Use -f to override." |
| 545 | fi |
| 546 | if [[ -z "${dependency}" ]]; then |
| 547 | dependency="${odependency}" |
| 548 | fi |
| 549 | |
| 550 | # Check out local reference branch; create it if needed. |
| 551 | # It will be retained since it may be needed to apply reverts |
| 552 | # prior to executing the merge. |
| 553 | # It is the responsibility of the user to remove it after it is |
| 554 | # no longer needed. |
| 555 | # Note: git rev-parse returns success if ${obranch} includes an |
| 556 | # abbreviated SHA. It also returns success if a _remote_ branch |
| 557 | # with the same name exists. So let's use show-ref instead. |
| 558 | # if ! git rev-parse --verify --quiet "${obranch}"; then |
| 559 | if ! git show-ref --verify --quiet "refs/heads/${obranch}"; then |
| 560 | if ! git checkout -b "${obranch}" "${tag}"; then |
| 561 | die "Failed to create '${obranch}' from '${tag}'." |
| 562 | fi |
| 563 | else |
| 564 | if ! git checkout "${obranch}"; then |
| 565 | die "Failed to check out '${obranch}'." |
| 566 | fi |
| 567 | fi |
| 568 | |
| 569 | if [[ ${prepare} -ne 0 ]]; then |
| 570 | # Push reference branch as well as the tag into the CrOS repository. |
| 571 | # Assume linear changes only; if the reference branch is reparented, |
| 572 | # the user has to explicitly update or remove the remote branch. |
| 573 | # Only push tag if it is a release tag; otherwise we neither want nor |
| 574 | # need it in the CrOS repository. |
| 575 | vtag=$(echo "${tag}" | grep -E "${PATTERN}") |
| 576 | if [[ -n "${vtag}" ]]; then |
| 577 | git push ${git_skip_validation} --no-verify ${dryrun} "${chromeos}" "refs/tags/${tag}" |
| 578 | else |
| 579 | echo "${tag} is not a release tag, not pushed" |
| 580 | fi |
| 581 | if ! git push ${git_skip_validation} --no-verify ${dryrun} "${chromeos}" "${obranch}"; then |
| 582 | die "Failed to upload '${obranch}' into '${chromeos}'." |
| 583 | fi |
| 584 | fi |
| 585 | } |
| 586 | |
| 587 | gitismerge() |
| 588 | { |
| 589 | local sha="$1" |
| 590 | local msha |
| 591 | |
| 592 | msha=$(git rev-list -1 --merges "${sha}"~1.."${sha}") |
| 593 | [[ -n "$msha" ]] |
| 594 | } |
| 595 | |
| 596 | # Apply patches from gerrit CLs into merge branch. |
| 597 | do_apply_changes() { |
| 598 | local cl |
| 599 | local ref |
| 600 | |
| 601 | for cl in "${changes[@]}"; do |
| 602 | echo "Applying CL:${cl}" |
| 603 | ref=$(dequote "$(gerrit --json search "change:${cl}" \ |
| 604 | | jq ".[].currentPatchSet.ref" | head -n1)") |
| 605 | if [[ -z "${ref}" ]]; then |
| 606 | die "Patch set for CL:${cl} not found." |
| 607 | fi |
| 608 | if ! git fetch "${chromeos}" "${ref}" >/dev/null 2>&1; then |
| 609 | die "Failed to fetch CL:${cl}." |
| 610 | fi |
| 611 | if gitismerge FETCH_HEAD; then |
| 612 | # git cherry-pick -m <parent> does not work since it pulls in |
| 613 | # the merge as single commit. This messes up history and was |
| 614 | # seen to result in obscure and avoidable conflicts. |
| 615 | if ! git merge --no-edit FETCH_HEAD; then |
| 616 | die "Failed to merge CL:${cl} into merge branch." |
| 617 | fi |
| 618 | else |
| 619 | if ! git cherry-pick FETCH_HEAD; then |
| 620 | die "Failed to cherry-pick CL:${cl} into merge branch." |
| 621 | fi |
| 622 | fi |
| 623 | done |
| 624 | } |
| 625 | |
Guenter Roeck | c98bb9a | 2019-08-02 12:57:46 -0700 | [diff] [blame^] | 626 | # Apply reverts from list of SHAs from merge branch prior to |
| 627 | # the actual merge |
| 628 | do_apply_reverts() { |
| 629 | local revert |
| 630 | |
| 631 | for revert in $*; do |
| 632 | echo "Reverting commit ${revert}" |
| 633 | if ! git revert --no-commit "${revert}"; then |
| 634 | die "Failed to revert commit ${revert} in merge branch." |
| 635 | fi |
| 636 | done |
| 637 | } |
| 638 | |
Guenter Roeck | 9aa6957 | 2017-12-12 13:36:30 -0800 | [diff] [blame] | 639 | # Do the merge. |
| 640 | # - Create merge branch. |
| 641 | # - Merge. |
| 642 | # - Handle conflicts [abort if there are unhandled conflicts]. |
| 643 | # - Create detailed merge commit log. |
| 644 | do_merge() { |
| 645 | # xbranch: Name of branch to merge. |
| 646 | # ref: Baseline reference for request-pull. |
| 647 | local xbranch |
| 648 | local ref |
| 649 | local patch |
Guenter Roeck | 7acc7c1 | 2019-08-02 12:28:37 -0700 | [diff] [blame] | 650 | local file |
| 651 | local files |
Guenter Roeck | c98bb9a | 2019-08-02 12:57:46 -0700 | [diff] [blame^] | 652 | local revert |
Guenter Roeck | 9aa6957 | 2017-12-12 13:36:30 -0800 | [diff] [blame] | 653 | |
| 654 | git branch -D "${mbranch}" >/dev/null 2>&1 |
| 655 | if ! git checkout -b "${mbranch}" "${ocbranch}"; then |
| 656 | die "Failed to create merge branch '${mbranch}'." |
| 657 | fi |
| 658 | |
| 659 | if [[ ${prepare} -eq 0 ]]; then |
| 660 | xbranch="${obranch}" |
| 661 | else |
| 662 | xbranch="${chromeos}/${obranch}" |
| 663 | fi |
| 664 | |
Guenter Roeck | c98bb9a | 2019-08-02 12:57:46 -0700 | [diff] [blame^] | 665 | if [[ -n "${prereverts[@]}" ]]; then |
| 666 | do_apply_reverts ${prereverts[@]} |
| 667 | fi |
| 668 | |
Guenter Roeck | 9aa6957 | 2017-12-12 13:36:30 -0800 | [diff] [blame] | 669 | do_apply_changes |
| 670 | ref=$(git rev-parse HEAD) |
| 671 | |
| 672 | # Do the merge. |
| 673 | # Use --no-ff to ensure this is always handled as merge, even for linear |
| 674 | # merges. Otherwise linear merges would succeed and move the branch HEAD |
| 675 | # forward even though --no-commit is specified. This lets us add an |
| 676 | # explicit merge commit log. |
| 677 | conflicts=() |
| 678 | if ! git merge --no-commit --no-ff "${xbranch}" > "${tmpfile}"; then |
| 679 | files=$(git rerere status) |
| 680 | if [[ -n "${files}" ]]; then |
Guenter Roeck | 7acc7c1 | 2019-08-02 12:28:37 -0700 | [diff] [blame] | 681 | error "Unresolved conflicts:" |
| 682 | for file in ${files}; do |
| 683 | echo " ${file}" |
| 684 | done |
| 685 | die "Please resolve conflicts, commit changes, and then rerun the merge script.\nMake sure you have 'git rerere' enabled." |
Guenter Roeck | 9aa6957 | 2017-12-12 13:36:30 -0800 | [diff] [blame] | 686 | fi |
| 687 | echo "All conflicts resolved, continuing" |
| 688 | conflicts=($(grep CONFLICT "${tmpfile}" \ |
| 689 | | sed -e 's/.*Merge conflict in //')) |
| 690 | fi |
| 691 | |
| 692 | # Note: The following is no longer needed in recent versions of git. |
| 693 | # Keep it around since it does not hurt. |
| 694 | if [[ ${#conflicts[@]} -gt 0 ]]; then |
| 695 | git add ${conflicts[*]} |
| 696 | fi |
| 697 | |
| 698 | for patch in "${patches[@]}"; do |
| 699 | if ! patch -p 1 < "${patch}" >"${tmpfile}"; then |
| 700 | die "Failed to apply patch ${patch}" |
| 701 | fi |
| 702 | if ! git add $(sed -e 's/.* //' "${tmpfile}"); then |
| 703 | die "Failed to add patched files to git commit list" |
| 704 | fi |
| 705 | done |
| 706 | |
| 707 | if ! git commit -s --no-edit; then |
| 708 | die "Failed to commit merge." |
| 709 | fi |
| 710 | |
Guenter Roeck | c98bb9a | 2019-08-02 12:57:46 -0700 | [diff] [blame^] | 711 | if [[ -n "${reverts[@]}" ]]; then |
| 712 | do_apply_reverts ${reverts[@]} |
| 713 | if ! git commit --amend --no-edit; then |
| 714 | die "Failed to commit merge after post-commit reverts." |
| 715 | fi |
| 716 | fi |
| 717 | |
Guenter Roeck | 9aa6957 | 2017-12-12 13:36:30 -0800 | [diff] [blame] | 718 | # Update commit message. |
| 719 | |
| 720 | ( echo "${Subject}" |
| 721 | echo |
| 722 | echo "Merge of ${tag} into ${cbranch}" |
| 723 | echo |
| 724 | ) > "${tmpfile}" |
| 725 | |
| 726 | # Add conflicts to description. |
| 727 | if [[ ${#conflicts[@]} -gt 0 ]]; then |
| 728 | ( |
| 729 | echo "Conflicts:" |
| 730 | for conflict in "${conflicts[@]}"; do |
| 731 | echo " ${conflict}" |
| 732 | done |
| 733 | echo |
| 734 | ) >> "${tmpfile}" |
| 735 | fi |
| 736 | |
Guenter Roeck | c98bb9a | 2019-08-02 12:57:46 -0700 | [diff] [blame^] | 737 | # Add reverts to description. |
| 738 | if [[ -n "${prereverts[@]}${reverts[@]}" ]]; then |
| 739 | ( |
| 740 | echo "The following patches have been reverted as part of the merge" |
| 741 | echo "to remove code which is obsolete or no longer applicable." |
| 742 | echo |
| 743 | for revert in ${prereverts[@]} ${reverts[@]}; do |
| 744 | echo " $(git show --oneline --no-decorate -s ${revert})" |
| 745 | done |
| 746 | echo |
| 747 | ) >> "${tmpfile}" |
| 748 | fi |
| 749 | |
Guenter Roeck | 9aa6957 | 2017-12-12 13:36:30 -0800 | [diff] [blame] | 750 | if [[ -n "$(git log --oneline "${tag}..${obranch}")" ]]; then |
| 751 | ( echo "Changes applied on top of '${tag}' prior to merge:" |
| 752 | git log --oneline --no-decorate "${tag}..${obranch}" | \ |
| 753 | sed -e 's/^/ /' |
| 754 | echo |
| 755 | ) >> "${tmpfile}" |
| 756 | fi |
| 757 | |
| 758 | ( echo "Changelog:" |
| 759 | git request-pull "${ref}" . | \ |
| 760 | sed -n '/^--------------/,$p' |
| 761 | |
| 762 | echo |
| 763 | |
Guenter Roeck | 33f263a | 2019-06-28 08:51:23 -0700 | [diff] [blame] | 764 | echo "${bug}" |
| 765 | echo "TEST=Build and test on various affected systems" |
| 766 | echo |
Guenter Roeck | 9aa6957 | 2017-12-12 13:36:30 -0800 | [diff] [blame] | 767 | if [[ -n "${dependency}" ]]; then |
| 768 | echo "${dependency}" |
| 769 | fi |
Guenter Roeck | 9aa6957 | 2017-12-12 13:36:30 -0800 | [diff] [blame] | 770 | if [[ -n "${changeid}" ]]; then |
Guenter Roeck | 9aa6957 | 2017-12-12 13:36:30 -0800 | [diff] [blame] | 771 | echo "${changeid}" |
| 772 | fi |
| 773 | ) >> "${tmpfile}" |
| 774 | |
| 775 | # Amend commit with the updated description. |
| 776 | if ! git commit -s --amend -F "${tmpfile}"; then |
| 777 | die "Failed to amend merge with commit log." |
| 778 | fi |
| 779 | } |
| 780 | |
| 781 | do_notify() { |
| 782 | local cl |
| 783 | local email_cc |
| 784 | local cc_notify_cc |
| 785 | local subject |
| 786 | local message |
| 787 | local lbug |
| 788 | local tdeadline |
| 789 | |
| 790 | if [[ -z "${merge_cl}" ]]; then |
| 791 | die "No merge CL, can not send notifications." |
| 792 | fi |
| 793 | |
| 794 | gerrit --json search "change:${merge_cl}" > "${tmpfile}" |
| 795 | |
| 796 | cl=$(dequote "$(jq ".[].number" "${tmpfile}")") |
| 797 | if [[ -z "${cl}" ]]; then |
| 798 | die "Missing CL for topic '${topic}' (upload into gerrit first)." |
| 799 | fi |
| 800 | |
| 801 | subject=$(dequote "$(jq ".[].subject" "${tmpfile}")") |
| 802 | message=$(dequote "$(jq ".[].commitMessage" "${tmpfile}" \ |
| 803 | | sed -e 's/\\n/\n/g')") |
| 804 | email_cc=$(dequote "$(jq ".[].owner.email" "${tmpfile}")") |
| 805 | if [[ -n "${email_cc}" ]]; then |
| 806 | email_cc="-cc=${email_cc}" |
| 807 | fi |
| 808 | |
| 809 | if [[ -n "${notify_cc}" ]]; then |
| 810 | cc_notify_cc="--cc=${notify_cc}" |
| 811 | fi |
| 812 | |
| 813 | if [[ "${bug##BUG=b:}" != "${bug}" ]]; then # buganizer |
| 814 | lbug="https://b.corp.google.com/${bug##BUG=b:}" |
| 815 | elif [[ "${bug##BUG=chromium:}" != "${bug}" ]]; then # crbug |
| 816 | lbug="https://crbug.com/${bug##BUG=chromium:}" |
| 817 | else # unknown/invalid |
| 818 | lbug="${bug##BUG=}" |
| 819 | fi |
| 820 | |
| 821 | tdeadline=$(($(date +%s) + deadline * 86400)) |
| 822 | |
| 823 | cat <<-EOF > "${tmpfile}" |
| 824 | Subject: Review request: "${subject}" |
| 825 | |
| 826 | This is the start of the review cycle for the merge of stable release |
| 827 | ${tag} into ${cbranch}. If anyone has issues or concerns |
| 828 | with this stable release being applied, please let me know. |
| 829 | |
| 830 | Bug: ${lbug} |
| 831 | Code review: https://chromium-review.googlesource.com/#/q/${cl} |
| 832 | |
| 833 | Responses should be made by $(date --date="@${tdeadline}"). |
| 834 | Anything received after that time might be too late. |
| 835 | |
| 836 | Commit message and changelog are as follows. |
| 837 | |
| 838 | ${message} |
| 839 | EOF |
| 840 | |
| 841 | if ! git send-email ${dryrun} --to="${notify_to}" "${cc_notify_cc}" "${email_cc}" \ |
| 842 | --8bit-encoding="UTF-8" \ |
| 843 | --suppress-cc=all "${tmpfile}"; then |
| 844 | die "Failed to send notification e-mail to '${notify_to}'." |
| 845 | fi |
| 846 | } |
| 847 | |
| 848 | do_upload() { |
| 849 | if [[ ${upload} -ne 0 ]]; then |
| 850 | if ! git push --no-verify ${dryrun} "${chromeos}" "${mbranch}:refs/for/${cbranch}%t=${topic}"; then |
| 851 | die "Failed to upload changes into '${chromeos}'." |
| 852 | fi |
| 853 | elif [[ ${prepare} -ne 0 ]]; then |
| 854 | echo "Push into ${chromeos} using the following command:" |
| 855 | echo " git push --no-verify ${chromeos} ${mbranch}:refs/for/${cbranch}%t=${topic}" |
| 856 | fi |
| 857 | } |
| 858 | |
| 859 | main() { |
| 860 | do_getparams "$@" |
| 861 | do_validate |
| 862 | do_validate_changeids |
| 863 | do_setup |
| 864 | do_prepare |
| 865 | if [[ ${skip_merge} -eq 0 ]]; then |
| 866 | do_merge |
| 867 | do_upload |
| 868 | fi |
| 869 | if [[ ${notify} -ne 0 ]]; then |
| 870 | find_merge_cl |
| 871 | do_notify |
| 872 | fi |
| 873 | |
| 874 | exit 0 |
| 875 | } |
| 876 | |
| 877 | main "$@" |