| #!/bin/bash |
| # |
| # Copyright 2018 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| # |
| # Script to merge upstream tags into chromeos. |
| # The command creates a new branch with merge results. |
| # If necessary, it also pushes the tag into the remote |
| # repository and creates a branch pointing to it. |
| |
| readonly notify_to="chromeos-kernel@google.com" |
| readonly notify_cc="chromium-os-reviews@chromium.org" |
| |
| # Valid tag pattern. |
| PATTERN="^v[2-9](\.[0-9]+)+$" |
| PATTERN_RC="^v[2-9](\.[0-9]+)+-rc$" |
| GLOB="v[2-9].*[0-9]" |
| NUMBER="^[1-9][0-9]*$" |
| |
| # Initial parameter values. |
| changeid="" # Get Change-Id from CL or generate new Change-Id. |
| bug="" # Get Bug-Id from CL or provide on on command line. |
| tag="" # Tag to merge; must be provided on command line. |
| force=0 # Do not override Change-Id / Bug-Id. |
| prepare=0 # Do not prepare for upload. |
| upload=0 # Do not upload into Gerrit. |
| do_dryrun=0 # If 1, don't push anything upstream, don't send email. |
| notify=0 # Do not send notification e-mail. |
| deadline=3 # Feedback deadline (in days, default 3). |
| changes=() # List of uncommitted CLs to be applied prior to merge. |
| patches=() # List of patches to apply before committing merge. |
| reverts=() # List of patches to revert as part of merge, after merge |
| prereverts=() # List of patches to revert as part of merge, prior to merge |
| dependency="" # No dependency |
| Subject="" # default subject |
| namespace="" # default namespace |
| |
| # derived parameters |
| skip_merge=0 # Skip actual merge and upload. |
| # Will be set if tag has already been merged and force is true. |
| |
| readonly tmpfile=$(mktemp) |
| |
| trap 'rm -f "${tmpfile}"' EXIT |
| trap 'exit 2' SIGHUP SIGINT SIGQUIT SIGTERM |
| |
| error() { |
| printf '%b: error: %b\n' "${0##*/}" "$*" >&2 |
| } |
| |
| die() { |
| error "$@" |
| exit 1 |
| } |
| |
| usage() { |
| cat <<-EOF |
| Usage: ${0##*/} [options] tag |
| |
| Parameters: |
| tag Tag, branch, or SHA to merge. Must be either a valid stable |
| branch release tag, a valid branch name, or a valid SHA. |
| |
| Options: |
| -b bug-id[,bug-id] ... |
| Bug-id or list of bug IDs. Must be valid buganizer |
| bug ID. Mandatory unless the merge branch already exists |
| locally or in Gerrit. |
| -c change-id Change-Id as used by Gerrit. Optional. |
| -d deadline Feedback deadline in days (default: ${deadline}) |
| -f Force. Override existing Change-Id and bug number. |
| -h Display help text and exit. |
| -l change-id Apply patch extracted from CL:change-id prior to merge. |
| May be repeated multiple times. |
| -n Send notification e-mail to ${notify_to}. |
| -N namespace Namespace (branch prefix) to use |
| Default 'stable-merge/linux' or 'merge', depending on context |
| -q dependency Add dependency (Cq-Depend: <dependency>) |
| -p Prepare for upload into Gerrit. Implied if -u is specified. |
| -r Name of branch to base merge on. Determined from stable |
| release tag or from target branch name if not provided. |
| Must be existing local branch. Will be pushed into gerrit |
| as part of the merge process if not already available in |
| gerrit, and has to follow gerrit commit rules. |
| -P sha Revert patch <sha> as part of merge, pre-merge |
| May be repeated multiple times. |
| -R sha Revert patch <sha> as part of merge, post-merge |
| May be repeated multiple times. |
| -s Simulate, or dry-run. Don't actually push anything into |
| gerrit, and don't send e-mails. |
| -S subject Replace default subject line with provided string |
| -t Target branch name. The branch must exist in the Chrome OS |
| repository. |
| -u Upload merge into Gerrit. |
| -x patchfile Patch to apply before committing merge. Patch will be applied |
| with "patch -p 1 < patchfile". May be repeated several times. |
| EOF |
| |
| if [[ $# -gt 0 ]]; then |
| echo |
| die "$@" |
| fi |
| exit 0 |
| } |
| |
| # Find and report remote. |
| find_remote() { |
| local url="$1" |
| local remote |
| |
| for remote in $(git remote 2>/dev/null); do |
| rurl=$(git remote get-url "${remote}") |
| # ignore trailing '/' when comparing repositories |
| if [[ "${rurl%/}" == "${url%/}" ]]; then |
| echo "${remote}" |
| break |
| fi |
| done |
| } |
| |
| # Find remote. If there is no remote pointing to the referenced |
| # kernel repository, create one. |
| find_create_remote() { |
| local url="$1" |
| local default="$2" |
| local result |
| |
| result="$(find_remote "${url}")" |
| if [[ -z "${result}" ]]; then |
| git remote add "${default}" "${url}" |
| result="${default}" |
| fi |
| |
| echo "${result}" |
| } |
| |
| # Find and report CrOS remote. |
| # This is useful if the command runs on a checked out |
| # tree with several remotes. |
| find_chromeos() { |
| local url="https://chromium.googlesource.com/chromiumos/third_party/kernel" |
| |
| find_remote "${url}" |
| } |
| |
| # Find stable remote. If there is no remote pointing to the stable |
| # kernel repository, create one. |
| find_stable() { |
| local url="git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git" |
| |
| find_create_remote "${url}" "stable" |
| } |
| |
| # Find stable remote. If there is no remote pointing to the stable |
| # kernel repository, create one. |
| find_stable_rc() { |
| local url="git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable-rc.git" |
| |
| find_create_remote "${url}" "stable-rc" |
| } |
| |
| do_getparams() { |
| local bugs="" # List of bugs |
| local nbug="" # Numerical part of bug #, for validation. |
| local _bug |
| local option |
| local vtag |
| |
| while getopts "b:c:d:fhl:N:nP:pq:r:R:st:uS:x:" option; do |
| case ${option} in |
| b) bugs="${OPTARG}" ;; |
| c) changeid="Change-Id: ${OPTARG}" ;; |
| d) deadline="${OPTARG}" |
| if ! [[ "${deadline}" =~ ${NUMBER} ]]; then |
| die "Deadline must be numeric value > 0 (${deadline})" |
| fi |
| ;; |
| f) force=1 ;; |
| l) changes+=("${OPTARG}") ;; |
| n) notify=1 ;; |
| N) namespace="${OPTARG}" ;; |
| p) prepare=1 ;; |
| q) dependency="${OPTARG}" ;; |
| r) rbranch="${OPTARG}" ;; |
| P) prereverts+=("${OPTARG}") ;; |
| R) reverts+=("${OPTARG}") ;; |
| t) tbranch="${OPTARG}" ;; |
| s) do_dryrun=1 ;; |
| S) Subject="${OPTARG}" ;; |
| u) upload=1 prepare=1 ;; |
| x) patches+=("${OPTARG}") ;; |
| h|?|*) usage ;; |
| esac |
| done |
| shift $((OPTIND - 1)) |
| tag=$1 |
| if [[ -z "${tag}" ]]; then |
| usage "tag parameter is mandatory" |
| fi |
| vtag=$(echo "${tag}" | grep -E "${PATTERN}") |
| if [[ "${tag}" != "${vtag}" ]]; then |
| # Not a stable release tag, meaning we can not get it from a stable release. |
| # Maybe it is a stable release candidate. |
| vtag=$(echo "${tag}" | grep -E "${PATTERN_RC}") |
| if [[ "${tag}" != "${vtag}" ]]; then |
| # Make sure that the reference exists and bail out if not. |
| if ! git rev-parse --verify "${tag}" >/dev/null 2>&1; then |
| die "Unknown reference '${tag}'." |
| fi |
| else |
| die "${tag} references a stable release candidate. Not supported yet." |
| fi |
| fi |
| if [[ -n "${rbranch}" ]]; then |
| if ! git rev-parse --verify "${rbranch}" >/dev/null 2>&1; then |
| die "No such branch: ${rbranch}." |
| fi |
| fi |
| |
| if [[ -n "${bugs}" ]]; then |
| for _bug in ${bugs//,/ }; do |
| if [[ "${_bug}" == b:* ]]; then # buganizer |
| nbug="${_bug##b:}" |
| elif [[ "${_bug}" == b/* ]]; then # buganizer, alternative |
| nbug="${_bug##b/}" |
| else # crbug etc are not allowed. |
| die "Invalid bug ID '${_bug}'." |
| fi |
| if [[ ! "${nbug}" =~ ${NUMBER} ]]; then |
| die "Invalid bug ID '${_bug}'." |
| fi |
| done |
| bug="BUG=${bugs}" |
| fi |
| dependency="${dependency:+Cq-Depend: ${dependency}}" |
| } |
| |
| # Validate environment and repository. |
| # We need a couple of commands, the repository must be |
| # a CrOS kernel repository, and it must be clean. |
| do_validate() { |
| local gerrit |
| local chromeos |
| local jq |
| |
| gerrit=$(which gerrit) |
| if [[ -z "${gerrit}" ]]; then |
| die "gerrit is required. Get from chromite or run from chroot." |
| fi |
| jq=$(which jq) |
| if [[ -z ${jq} ]]; then |
| die "jq is required. Install (apt-get install jq) or run from chroot." |
| fi |
| chromeos=$(find_chromeos) |
| if [[ -z ${chromeos} ]]; then |
| die "$(pwd) is not a Chromium OS kernel repository." |
| fi |
| if [[ -n "$(git status -s)" ]]; then |
| die "Requires clean repository." |
| fi |
| if [[ -n "${tbranch}" ]]; then |
| if ! git rev-parse --verify "${chromeos}/${tbranch}" >/dev/null 2>&1; then |
| die "No such branch: ${chromeos}/${tbranch}." |
| fi |
| fi |
| } |
| |
| # Validate provided Change-IDs. |
| do_validate_changeids() { |
| local cl |
| local ref |
| |
| for cl in "${changes[@]}"; do |
| ref=$(gerrit --json search "change:${cl}" \ |
| | jq ".[].currentPatchSet.ref") |
| if [[ -z "${ref}" ]]; then |
| die "No such Change-Id: ${cl}." |
| fi |
| done |
| } |
| |
| # Initialize global variables, plus some more validation. |
| do_setup() { |
| readonly stable=$(find_stable) |
| readonly stable_rc=$(find_stable_rc) |
| local vtag |
| local dvtag |
| |
| # If a stable release tag is provided, we need to update stable |
| # at this point to get the tag if it is not already available. |
| vtag=$(echo "${tag}" | grep -E "${PATTERN}") |
| if [[ "${tag}" == "${vtag}" ]]; then |
| if ! git rev-parse --verify "${tag}" >/dev/null 2>&1; then |
| if ! git fetch "${stable}" > /dev/null 2>&1; then |
| die "Failed to update stable release." |
| fi |
| if ! git rev-parse --verify "${tag}" >/dev/null 2>&1; then |
| die "Reference ${tag} not available." |
| fi |
| fi |
| else |
| # This might be a stable release candidate. |
| vtag=$(echo "${tag}" | grep -E "${PATTERN_RC}") |
| if [[ "${tag}" == "${vtag}" ]]; then |
| git fetch "${stable_rc}" > /dev/null 2>&1 |
| # The stable release tag is "vX.Y.Z-rc". Stable release candidate |
| # branches are named "remote/linux-X.Y.y". |
| # Extract 'X' and 'Y', create the remote branch name, |
| # clone/update the remote branch, and set a matching tag |
| # on top of it. |
| |
| die "Stable release candidates are not yet supported." |
| fi |
| fi |
| |
| readonly ctag=$(git describe --match "${GLOB}" --abbrev=0 "${tag}" \ |
| 2>/dev/null | cut -f1,2 -d. | sed -e 's/v//') |
| readonly dtag=$(git describe --tags "${tag}") |
| |
| # While we accept any valid reference as <tag>, we want it to be based |
| # on an existing release tag. |
| dbtag=${dtag%%-*} |
| dvtag=$(git describe --tags --abbrev=0 "${dtag}") |
| if [[ "${dbtag}" != "${dvtag}" ]]; then |
| die "${tag} (${dtag}) is not based on an existing release tag." |
| fi |
| |
| readonly chromeos=$(find_chromeos) |
| if [[ -z "${chromeos}" ]]; then |
| die "Chromium OS kernel repository not found." |
| fi |
| |
| # cbranch: Chromeos branch name |
| # mcbranch: local copy (baseline) |
| # ocbranch: remote (target) branch |
| # |
| # Note: This assumes that the target repository is ${chromeos}, |
| # even if a remote branch has been specified. It might make sense |
| # to make this configurable. |
| if [[ -n "${tbranch}" ]]; then |
| readonly cbranch="${tbranch}" |
| else |
| readonly cbranch="chromeos-${ctag}" |
| fi |
| if [[ -n "${rbranch}" ]]; then |
| readonly ocbranch="${rbranch}" |
| else |
| readonly ocbranch="${chromeos}/${cbranch}" |
| fi |
| |
| readonly mcbranch="merge/${cbranch}" |
| |
| # Topic to use. |
| readonly topic="merge-${dtag}" |
| |
| if ! git rev-parse --verify "${ocbranch}" >/dev/null 2>&1; then |
| usage "Invalid tag '${tag}': No such branch: '${ocbranch}'" |
| fi |
| |
| # mbranch: Local branch used to execute the merge. |
| readonly mbranch="${mcbranch}-${dtag}" |
| |
| # Determine namespace to use if not provided |
| if [[ -z "${namespace}" ]]; then |
| if [[ "${tag}" == "${vtag}" ]]; then |
| namespace="stable-merge/linux" |
| else |
| namespace="merge" |
| fi |
| fi |
| |
| # obranch: chromeos branch used as reference. |
| # May include local reverts from merge if necessary. |
| # If necessary, a branch with this name will be created locally and |
| # in the chromeos repository. It is necessary to perform the merge. |
| readonly obranch="${namespace}/${dtag}" |
| |
| if [[ ${do_dryrun} -ne 0 ]]; then |
| readonly dryrun="--dry-run" |
| fi |
| |
| Subject="CHROMIUM: ${Subject:-Merge '${tag}' into ${cbranch}}" |
| } |
| |
| have_version() { |
| local tag |
| local tot_tag |
| local index |
| local v1 |
| local v2 |
| local vtag |
| |
| tag=$1 |
| vtag=$(echo "${tag}" | grep -E "${PATTERN}") |
| if [[ "${tag}" != "${vtag}" ]]; then |
| # Not a release tag, can not evaluate. |
| return 0 |
| fi |
| |
| tot_tag=$(git describe --match "v[2-9].*[0-9]" --abbrev=0 "${ocbranch}") |
| |
| index=1 |
| while true; do |
| v1=$(echo "${tag}" | cut -f${index} -d. | sed -e 's/[^0-9]//g') |
| v2=$(echo "${tot_tag}" | cut -f${index} -d. | sed -e 's/[^0-9]//g') |
| # If both version numbers are empty, we reached the end of the |
| # version number string, and the versions are equal. |
| # Return true. |
| if [[ -z "${v1}" && -z "${v2}" ]]; then |
| return 1 |
| fi |
| # Interpret empty minor version numbers as version 0. |
| if [[ -z "${v1}" ]]; then |
| v1=0 |
| fi |
| if [[ -z "${v2}" ]]; then |
| v2=0 |
| fi |
| # If ToT version is larger than tag, return true. |
| if [[ ${v2} -gt ${v1} ]]; then |
| return 1 |
| fi |
| # If tag version is targer than ToT, return false. |
| if [[ ${v2} -lt ${v1} ]]; then |
| return 0 |
| fi |
| index=$((index + 1)) |
| done |
| } |
| |
| # Remove double quotes from beginning and end of a string, and |
| # remove the escape character from double quotes within the string. |
| dequote() { |
| local tmp="${1#\"}" # beginning |
| tmp="${tmp%\"}" # end |
| echo "${tmp//\\\"/\"}" # remove embedded escape characters |
| } |
| |
| # Try to find the merge CL. |
| # Walk through all CLs tagged with the merge topic |
| # and try to find one with the expected subject line. |
| # If found, set merge_cl to the respective value for later use. |
| find_merge_cl() { |
| local cls |
| local cl |
| local subject |
| |
| cls=($(gerrit --json search "hashtag:${topic}" \ |
| | jq ".[].number" | sed -e 's/"//g')) |
| |
| for cl in "${cls[@]}"; do |
| subject=$(dequote "$(gerrit --json search "change:${cl}" \ |
| | jq ".[].subject")") |
| if [[ "${subject}" == "${Subject}" ]]; then |
| merge_cl="${cl}" |
| break |
| fi |
| done |
| } |
| |
| # Prepare for merge. |
| # - Update remotes. |
| # - Verify that tag exists. |
| # - Search for merge in gerrit. If it exists, validate bug ID and Change-Id. |
| # - Push tag and reference branch into CrOS repository if necessary. |
| do_prepare() { |
| local vtag |
| local obug |
| local ochangeid |
| local odependency |
| local ref |
| |
| find_merge_cl |
| |
| printf "Updating ${chromeos}..." |
| git fetch "${chromeos}" > /dev/null |
| printf "\nUpdating ${mcbranch} ..." |
| if git rev-parse --verify "${mcbranch}" >/dev/null 2>&1; then |
| if ! git checkout "${mcbranch}" >/dev/null 2>&1; then |
| die "Failed to check out '${mcbranch}'." |
| fi |
| git pull >/dev/null |
| else |
| if ! git checkout -b "${mcbranch}" "${ocbranch}"; then |
| die "Failed to create '${mcbranch}' from '${ocbranch}'." |
| fi |
| fi |
| echo |
| |
| # Abort if chromeos already includes the tag unless 'force' is set. |
| if ! have_version "${dtag}"; then |
| if [[ ${force} -eq 0 ]]; then |
| die "Tag or reference '${tag}' already in '${ocbranch}'." |
| fi |
| echo "Warning: Tag '${tag}' already in '${ocbranch}'." |
| echo "Will not merge/notify/prepare/upload." |
| skip_merge=1 |
| prepare=0 |
| notify=0 |
| upload=0 |
| fi |
| |
| if [[ -n "${merge_cl}" ]]; then |
| ref=$(dequote "$(gerrit --json search "change:${merge_cl}" \ |
| | jq ".[].currentPatchSet.ref")") |
| fi |
| if [[ -n "${ref}" ]]; then |
| if ! git fetch "${chromeos}" "${ref}" >/dev/null 2>&1; then |
| die "Failed to fetch '${ref}' from '${chromeos}'." |
| fi |
| git show -s --format=%B FETCH_HEAD > "${tmpfile}" |
| else |
| # We may have a local merge branch. |
| if git rev-parse --verify "${mbranch}" >/dev/null 2>&1; then |
| local subject |
| |
| # Make sure the branch actually includes the merge we are looking for. |
| git show -s --format=%B "${mbranch}" > "${tmpfile}" |
| subject="$(head -n 1 "${tmpfile}")" |
| if [[ "${subject}" != "${Subject}" ]]; then |
| rm -f "${tmpfile}" |
| touch "${tmpfile}" |
| fi |
| else |
| rm -f "${tmpfile}" |
| touch "${tmpfile}" |
| fi |
| fi |
| obug=$(grep "^BUG=" "${tmpfile}") |
| if [[ -n "${bug}" && -n "${obug}" && "${bug}" != "${obug}" \ |
| && ${force} -eq 0 ]]; then |
| die "Bug mismatch: '${bug}' <-> '${obug}'. Use -f to override." |
| fi |
| if [[ -z "${bug}" ]]; then |
| bug="${obug}" |
| fi |
| if [[ -z "${bug}" ]]; then |
| die "New merge: must specify bug ID." |
| fi |
| ochangeid=$(grep "^Change-Id:" "${tmpfile}") |
| if [[ -n "${changeid}" && -n "${ochangeid}" \ |
| && "${changeid}" != "${ochangeid}" && ${force} -eq 0 ]]; then |
| die "Change-Id mismatch: '${changeid}' <-> '${ochangeid}'. Use -f to override." |
| fi |
| if [[ -z "${changeid}" ]]; then |
| changeid="${ochangeid}" |
| fi |
| |
| odependency=$(grep "^Cq-Depend:" "${tmpfile}") |
| if [[ -n "${dependency}" && -n "${odependency}" && \ |
| "${dependency}" != "${odependency}" && ${force} -eq 0 ]]; then |
| die "Dependency mismatch: '${dependency}' <-> '${odependency}'. Use -f to override." |
| fi |
| if [[ -z "${dependency}" ]]; then |
| dependency="${odependency}" |
| fi |
| |
| # Check out local reference branch; create it if needed. |
| # It will be retained since it may be needed to apply reverts |
| # prior to executing the merge. |
| # It is the responsibility of the user to remove it after it is |
| # no longer needed. |
| # Note: git rev-parse returns success if ${obranch} includes an |
| # abbreviated SHA. It also returns success if a _remote_ branch |
| # with the same name exists. So let's use show-ref instead. |
| # if ! git rev-parse --verify --quiet "${obranch}"; then |
| if ! git show-ref --verify --quiet "refs/heads/${obranch}"; then |
| if ! git checkout -b "${obranch}" "${tag}"; then |
| die "Failed to create '${obranch}' from '${tag}'." |
| fi |
| else |
| if ! git checkout "${obranch}"; then |
| die "Failed to check out '${obranch}'." |
| fi |
| fi |
| |
| if [[ ${prepare} -ne 0 ]]; then |
| # Push reference branch as well as the tag into the CrOS repository. |
| # Assume linear changes only; if the reference branch is reparented, |
| # the user has to explicitly update or remove the remote branch. |
| # Only push tag if it is a release tag; otherwise we neither want nor |
| # need it in the CrOS repository. |
| vtag=$(echo "${tag}" | grep -E "${PATTERN}") |
| if [[ -n "${vtag}" ]]; then |
| git push --no-verify ${dryrun} "${chromeos}" "refs/tags/${tag}" |
| else |
| echo "${tag} is not a release tag, not pushed" |
| fi |
| if ! git push --no-verify ${dryrun} "${chromeos}" "${obranch}"; then |
| die "Failed to upload '${obranch}' into '${chromeos}'." |
| fi |
| fi |
| } |
| |
| gitismerge() |
| { |
| local sha="$1" |
| local msha |
| |
| msha=$(git rev-list -1 --merges "${sha}"~1.."${sha}") |
| [[ -n "$msha" ]] |
| } |
| |
| # Apply patches from gerrit CLs into merge branch. |
| do_apply_changes() { |
| local cl |
| local ref |
| |
| for cl in "${changes[@]}"; do |
| echo "Applying CL:${cl}" |
| ref=$(dequote "$(gerrit --json search "change:${cl}" \ |
| | jq ".[].currentPatchSet.ref" | head -n1)") |
| if [[ -z "${ref}" ]]; then |
| die "Patch set for CL:${cl} not found." |
| fi |
| if ! git fetch "${chromeos}" "${ref}" >/dev/null 2>&1; then |
| die "Failed to fetch CL:${cl}." |
| fi |
| if gitismerge FETCH_HEAD; then |
| # git cherry-pick -m <parent> does not work since it pulls in |
| # the merge as single commit. This messes up history and was |
| # seen to result in obscure and avoidable conflicts. |
| if ! git merge --no-edit FETCH_HEAD; then |
| die "Failed to merge CL:${cl} into merge branch." |
| fi |
| else |
| if ! git cherry-pick FETCH_HEAD; then |
| die "Failed to cherry-pick CL:${cl} into merge branch." |
| fi |
| fi |
| done |
| } |
| |
| # Apply reverts from list of SHAs from merge branch prior to |
| # the actual merge |
| do_apply_reverts() { |
| local revert |
| |
| for revert in $*; do |
| echo "Reverting commit ${revert}" |
| if ! git revert --no-commit "${revert}"; then |
| die "Failed to revert commit ${revert} in merge branch." |
| fi |
| done |
| } |
| |
| # Do the merge. |
| # - Create merge branch. |
| # - Merge. |
| # - Handle conflicts [abort if there are unhandled conflicts]. |
| # - Create detailed merge commit log. |
| do_merge() { |
| # xbranch: Name of branch to merge. |
| # ref: Baseline reference for request-pull. |
| local xbranch |
| local ref |
| local patch |
| local file |
| local files |
| local revert |
| local content_conflicts |
| local delete_conflicts |
| |
| git branch -D "${mbranch}" >/dev/null 2>&1 |
| if ! git checkout -b "${mbranch}" "${ocbranch}"; then |
| die "Failed to create merge branch '${mbranch}'." |
| fi |
| |
| if [[ ${prepare} -eq 0 ]]; then |
| xbranch="${obranch}" |
| else |
| xbranch="${chromeos}/${obranch}" |
| fi |
| |
| if [[ -n "${prereverts[@]}" ]]; then |
| do_apply_reverts ${prereverts[@]} |
| fi |
| |
| do_apply_changes |
| ref=$(git rev-parse HEAD) |
| |
| # Do the merge. |
| # Use --no-ff to ensure this is always handled as merge, even for linear |
| # merges. Otherwise linear merges would succeed and move the branch HEAD |
| # forward even though --no-commit is specified. This lets us add an |
| # explicit merge commit log. |
| content_conflicts=() |
| delete_conflicts=() |
| if ! git merge --no-commit --no-ff "${xbranch}" > "${tmpfile}"; then |
| files=$(git rerere status) |
| if [[ -n "${files}" ]]; then |
| error "Unresolved conflicts:" |
| for file in ${files}; do |
| echo " ${file}" |
| done |
| die "Please resolve conflicts, commit changes, and then rerun the merge script.\nMake sure you have 'git rerere' enabled." |
| fi |
| echo "All conflicts resolved, continuing" |
| content_conflicts=($(grep -e 'CONFLICT.*content' "${tmpfile}" \ |
| | sed -e 's/.*Merge conflict in //')) |
| delete_conflicts=($(grep -e 'CONFLICT.*delete' "${tmpfile}" \ |
| | awk '{print $3;}' )) |
| fi |
| |
| # Note: The following is no longer needed in recent versions of git. |
| # Keep it around since it does not hurt. |
| if [[ ${#content_conflicts[@]} -gt 0 ]]; then |
| git add ${content_conflicts[*]} |
| fi |
| # Now handle deleted files |
| if [[ ${#delete_conflicts[@]} -gt 0 ]]; then |
| echo removing ${delete_conflicts[*]} |
| git rm ${delete_conflicts[*]} |
| fi |
| |
| for patch in "${patches[@]}"; do |
| if ! patch -p 1 < "${patch}" >"${tmpfile}"; then |
| die "Failed to apply patch ${patch}" |
| fi |
| if ! git add $(sed -e 's/.* //' "${tmpfile}"); then |
| die "Failed to add patched files to git commit list" |
| fi |
| done |
| |
| if ! git commit -s --no-edit; then |
| die "Failed to commit merge." |
| fi |
| |
| if [[ -n "${reverts[@]}" ]]; then |
| do_apply_reverts ${reverts[@]} |
| if ! git commit --amend --no-edit; then |
| die "Failed to commit merge after post-commit reverts." |
| fi |
| fi |
| |
| # Update commit message. |
| |
| ( echo "${Subject}" |
| echo |
| echo "Merge of ${tag} into ${cbranch}" |
| echo |
| ) > "${tmpfile}" |
| |
| # Add conflicts to description. |
| if [[ ${#content_conflicts[@]} -gt 0 ]]; then |
| ( |
| echo "Conflicts:" |
| for conflict in "${content_conflicts[@]}"; do |
| echo " ${conflict}" |
| done |
| echo |
| ) >> "${tmpfile}" |
| fi |
| |
| if [[ ${#delete_conflicts[@]} -gt 0 ]]; then |
| ( |
| echo "Files deleted during merge which were locally modified:" |
| for conflict in "${delete_conflicts[@]}"; do |
| echo " ${conflict}" |
| done |
| echo |
| ) >> "${tmpfile}" |
| fi |
| |
| # Add reverts to description. |
| if [[ -n "${prereverts[@]}${reverts[@]}" ]]; then |
| ( |
| echo "The following patches have been reverted as part of the merge" |
| echo "to remove code which is obsolete or no longer applicable." |
| echo |
| for revert in ${prereverts[@]} ${reverts[@]}; do |
| echo " $(git show --oneline --no-decorate -s ${revert})" |
| done |
| echo |
| ) >> "${tmpfile}" |
| fi |
| |
| if [[ -n "$(git log --oneline "${tag}..${obranch}")" ]]; then |
| ( echo "Changes applied on top of '${tag}' prior to merge:" |
| git log --oneline --no-decorate "${tag}..${obranch}" | \ |
| sed -e 's/^/ /' |
| echo |
| ) >> "${tmpfile}" |
| fi |
| |
| ( echo "Changelog:" |
| git request-pull "${ref}" . | \ |
| sed -n '/^--------------/,$p' |
| |
| echo |
| |
| echo "${bug}" |
| echo "TEST=Build and test on various affected systems" |
| echo |
| if [[ -n "${dependency}" ]]; then |
| echo "${dependency}" |
| fi |
| if [[ -n "${changeid}" ]]; then |
| echo "${changeid}" |
| fi |
| ) >> "${tmpfile}" |
| |
| # Amend commit with the updated description. |
| if ! git commit -s --amend -F "${tmpfile}"; then |
| die "Failed to amend merge with commit log." |
| fi |
| } |
| |
| do_notify() { |
| local cl |
| local email_cc |
| local cc_notify_cc |
| local subject |
| local message |
| local lbug |
| local tdeadline |
| |
| if [[ -z "${merge_cl}" ]]; then |
| die "No merge CL, can not send notifications." |
| fi |
| |
| gerrit --json search "change:${merge_cl}" > "${tmpfile}" |
| |
| cl=$(dequote "$(jq ".[].number" "${tmpfile}")") |
| if [[ -z "${cl}" ]]; then |
| die "Missing CL for topic '${topic}' (upload into gerrit first)." |
| fi |
| |
| subject=$(dequote "$(jq ".[].subject" "${tmpfile}")") |
| message=$(dequote "$(jq ".[].commitMessage" "${tmpfile}" \ |
| | sed -e 's/\\n/\n/g')") |
| email_cc=$(dequote "$(jq ".[].owner.email" "${tmpfile}")") |
| if [[ -n "${email_cc}" ]]; then |
| email_cc="-cc=${email_cc}" |
| fi |
| |
| if [[ -n "${notify_cc}" ]]; then |
| cc_notify_cc="--cc=${notify_cc}" |
| fi |
| |
| if [[ "${bug##BUG=b:}" != "${bug}" ]]; then # buganizer |
| lbug="https://b.corp.google.com/${bug##BUG=b:}" |
| elif [[ "${bug##BUG=chromium:}" != "${bug}" ]]; then # crbug |
| lbug="https://crbug.com/${bug##BUG=chromium:}" |
| else # unknown/invalid |
| lbug="${bug##BUG=}" |
| fi |
| |
| tdeadline=$(($(date +%s) + deadline * 86400)) |
| |
| cat <<-EOF > "${tmpfile}" |
| Subject: Review request: "${subject}" |
| |
| This is the start of the review cycle for the merge of stable release |
| ${tag} into ${cbranch}. If anyone has issues or concerns |
| with this stable release being applied, please let me know. |
| |
| Bug: ${lbug} |
| Code review: https://chromium-review.googlesource.com/#/q/${cl} |
| |
| Responses should be made by $(date --date="@${tdeadline}"). |
| Anything received after that time might be too late. |
| |
| Commit message and changelog are as follows. |
| |
| ${message} |
| EOF |
| |
| if ! git send-email ${dryrun} --to="${notify_to}" "${cc_notify_cc}" "${email_cc}" \ |
| --8bit-encoding="UTF-8" \ |
| --suppress-cc=all "${tmpfile}"; then |
| die "Failed to send notification e-mail to '${notify_to}'." |
| fi |
| } |
| |
| do_upload() { |
| if [[ ${upload} -ne 0 ]]; then |
| if ! git push --no-verify ${dryrun} "${chromeos}" "${mbranch}:refs/for/${cbranch}%t=${topic}"; then |
| die "Failed to upload changes into '${chromeos}'." |
| fi |
| elif [[ ${prepare} -ne 0 ]]; then |
| echo "Push into ${chromeos} using the following command:" |
| echo " git push --no-verify ${chromeos} ${mbranch}:refs/for/${cbranch}%t=${topic}" |
| fi |
| } |
| |
| main() { |
| do_getparams "$@" |
| do_validate |
| do_validate_changeids |
| do_setup |
| do_prepare |
| if [[ ${skip_merge} -eq 0 ]]; then |
| do_merge |
| do_upload |
| fi |
| if [[ ${notify} -ne 0 ]]; then |
| find_merge_cl |
| do_notify |
| fi |
| |
| exit 0 |
| } |
| |
| main "$@" |