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