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