blob: 6b99cf543d733a8144eeeb31ce82ba5a1c57115d [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
41
42# derived parameters
43skip_merge=0 # Skip actual merge and upload.
44 # Will be set if tag has already been merged and force is true.
45
46readonly tmpfile=$(mktemp)
47
48trap 'rm -f "${tmpfile}"' EXIT
49trap 'exit 2' SIGHUP SIGINT SIGQUIT SIGTERM
50
51error() {
52 printf '%b: error: %b\n' "${0##*/}" "$*" >&2
53}
54
55die() {
56 error "$@"
57 exit 1
58}
59
60usage() {
61 cat <<-EOF
62Usage: ${0##*/} [options] tag
63
64Parameters:
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
68Options:
69 -b bug-id[,bug-id] ...
William K Lin61683722021-03-22 06:07:53 +000070 Bug-id or list of bug IDs. Must be valid buganizer
Guenter Roeck9aa69572017-12-12 13:36:30 -080071 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 Roeck33f263a2019-06-28 08:51:23 -070080 -q dependency Add dependency (Cq-Depend: <dependency>)
Guenter Roeck9aa69572017-12-12 13:36:30 -080081 -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 Roeckc98bb9a2019-08-02 12:57:46 -070087 -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 Roeck9aa69572017-12-12 13:36:30 -080091 -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 Roeck7acc7c12019-08-02 12:28:37 -070097 -x patchfile Patch to apply before committing merge. Patch will be applied
98 with "patch -p 1 < patchfile". May be repeated several times.
Guenter Roeck9aa69572017-12-12 13:36:30 -080099EOF
100
101 if [[ $# -gt 0 ]]; then
102 echo
103 die "$@"
104 fi
105 exit 0
106}
107
108# Find and report remote.
109find_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.
125find_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.
142find_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.
150find_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.
158find_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
164do_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 Roeckc98bb9a2019-08-02 12:57:46 -0700171 while getopts "b:c:d:fhl:nP:pq:r:R:st:uS:x:" option; do
Guenter Roeck9aa69572017-12-12 13:36:30 -0800172 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 Roeckc98bb9a2019-08-02 12:57:46 -0700186 P) prereverts+=("${OPTARG}") ;;
187 R) reverts+=("${OPTARG}") ;;
Guenter Roeck9aa69572017-12-12 13:36:30 -0800188 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 Lin61683722021-03-22 06:07:53 +0000220
Guenter Roeck9aa69572017-12-12 13:36:30 -0800221 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 Lin61683722021-03-22 06:07:53 +0000227 else # crbug etc are not allowed.
228 die "Invalid bug ID '${_bug}'."
Guenter Roeck9aa69572017-12-12 13:36:30 -0800229 fi
230 if [[ ! "${nbug}" =~ ${NUMBER} ]]; then
231 die "Invalid bug ID '${_bug}'."
232 fi
233 done
234 bug="BUG=${bugs}"
235 fi
Guenter Roeck33f263a2019-06-28 08:51:23 -0700236 dependency="${dependency:+Cq-Depend: ${dependency}}"
Guenter Roeck9aa69572017-12-12 13:36:30 -0800237}
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.
242do_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.
270do_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.
284do_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 Roeck7acc7c12019-08-02 12:28:37 -0700374 Subject="CHROMIUM: ${Subject:-Merge '${tag}' into ${cbranch}}"
Guenter Roeck9aa69572017-12-12 13:36:30 -0800375}
376
377have_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.
425dequote() {
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.
435find_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.
458do_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 Roeck33f263a2019-06-28 08:51:23 -0700541 odependency=$(grep "^Cq-Depend:" "${tmpfile}")
Guenter Roeck9aa69572017-12-12 13:36:30 -0800542 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
587gitismerge()
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.
597do_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 Roeckc98bb9a2019-08-02 12:57:46 -0700626# Apply reverts from list of SHAs from merge branch prior to
627# the actual merge
628do_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 Roeck9aa69572017-12-12 13:36:30 -0800639# Do the merge.
640# - Create merge branch.
641# - Merge.
642# - Handle conflicts [abort if there are unhandled conflicts].
643# - Create detailed merge commit log.
644do_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 Roeck7acc7c12019-08-02 12:28:37 -0700650 local file
651 local files
Guenter Roeckc98bb9a2019-08-02 12:57:46 -0700652 local revert
Guenter Roeck9aa69572017-12-12 13:36:30 -0800653
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 Roeckc98bb9a2019-08-02 12:57:46 -0700665 if [[ -n "${prereverts[@]}" ]]; then
666 do_apply_reverts ${prereverts[@]}
667 fi
668
Guenter Roeck9aa69572017-12-12 13:36:30 -0800669 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 Roeck7acc7c12019-08-02 12:28:37 -0700681 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 Roeck9aa69572017-12-12 13:36:30 -0800686 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 Roeckc98bb9a2019-08-02 12:57:46 -0700711 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 Roeck9aa69572017-12-12 13:36:30 -0800718 # 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 Roeckc98bb9a2019-08-02 12:57:46 -0700737 # 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 Roeck9aa69572017-12-12 13:36:30 -0800750 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 Roeck33f263a2019-06-28 08:51:23 -0700764 echo "${bug}"
765 echo "TEST=Build and test on various affected systems"
766 echo
Guenter Roeck9aa69572017-12-12 13:36:30 -0800767 if [[ -n "${dependency}" ]]; then
768 echo "${dependency}"
769 fi
Guenter Roeck9aa69572017-12-12 13:36:30 -0800770 if [[ -n "${changeid}" ]]; then
Guenter Roeck9aa69572017-12-12 13:36:30 -0800771 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
781do_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}"
824Subject: Review request: "${subject}"
825
826This is the start of the review cycle for the merge of stable release
827${tag} into ${cbranch}. If anyone has issues or concerns
828with this stable release being applied, please let me know.
829
830Bug: ${lbug}
831Code review: https://chromium-review.googlesource.com/#/q/${cl}
832
833Responses should be made by $(date --date="@${tdeadline}").
834Anything received after that time might be too late.
835
836Commit message and changelog are as follows.
837
838${message}
839EOF
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
848do_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
859main() {
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
877main "$@"