blob: b6300335f0dccd2a99ae9ec1913ad51767c50005 [file] [log] [blame]
#!/bin/bash
# SPDX-License-Identifier: Apache-2.0
# Copyright 2021 Google LLC
#set -x # Uncomment to enable debugging
set -e
trap "tput smam; exit 1" SIGINT SIGTERM
REFRESH=""
ONLYREFRESH=""
function usage()
{
echo "Usage: $(basename $0): [--refresh|--refresh-patches|--only-refresh|--only-refresh-patches]"
exit 1
}
function print_red()
{
echo -e "\e[01;31m$@\e[0m"
}
function print_blue()
{
echo -e "\e[01;34m$@\e[0m"
}
function is_merge_commit()
{
local commit=${1}
if [ $(git show --no-patch --format="%p" ${commit} | wc -w) -gt 1 ]; then
return 0
else
return 1
fi
}
function read_series_file()
{
base_commit=$(cat patches/series | grep "# Applies onto" | awk '{print $5}') # Last Mainline commit we processed
last_commit=$(cat patches/series | grep "# Matches " | awk '{print $4}') # Last ${target} commit we processed
target=aosp/$(cat patches/series | grep "# Matches " | awk '{print $3}') # Remote branch (aosp/android-mainline)
status=$(cat patches/series | grep "# Status: " | awk '{print $3}') # Common patches branch status
UNTESTABLE=""
if [ "${status}" == "Untested" ]; then
UNTESTABLE=true
fi
if [ ! -z ${LOCAL} ]; then
target=$(echo ${target} | sed 's!aosp/!!')
if ! git branch | grep -q " ${target}$"; then
print_red "Local specified, but branch '${target}' does not exist locally"
exit 1
fi
fi
}
function sanity_check()
{
if [[ ! -L patches || ! -d patches || ! -e patches/series ]]; then
print_red "'patches' symlink to 'common-patches' repo is missing"
exit 1
fi
}
function check_and_update_remotes()
{
# Check public 'stable' / 'mainline' remotes are present
if ! git remote | grep -q "^stable$"; then
print_blue "Couldn't find repo 'stable' - adding it now"
git remote add stable git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git
fi
if ! git remote | grep -q "^mainline$"; then
print_blue "Couldn't find repo 'mainline' - adding it now"
git remote add mainline git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
fi
# ... and up-to-date
print_blue "Fetching from Mainline"
git fetch mainline
print_blue "Fetching from Stable"
git fetch stable
# For `repo checkout` managed repositories (see: https://source.android.com/setup/build/building-kernels)
if which repo > /dev/null && repo > /dev/null 2>&1; then
repo sync -n && git fetch --all
return 0
fi
# For non-`repo`/manually managed repositories
if ! git remote | grep -q "^aosp$"; then
print_blue "Couldn't find repo 'aosp' - adding it now"
git remote add aosp https://android.googlesource.com/kernel/common.git
fi
print_blue "Fetching from AOSP"
git fetch aosp
echo
}
function preamble()
{
check_and_update_remotes
if [ -d .git ]; then
DOTGIT=.git
elif [ -f .git ]; then
DOTGIT=$(cat .git | sed 's/gitdir: //')
else
print_red "Internal error: .git appears to be neither a regular file or a directory"
fi
if [ -z "${UNTESTABLE}" ]; then
# Ensure repo is in expected state
if ! git diff ${last_commit} --exit-code > /dev/null; then
print_blue "Tree is out of sync with 'common-patches' - resetting\n"
git reset --hard ${base_commit}
git quiltimport
fi
fi
LOCAL_LAST_COMMIT=$(git log -n1 --format=%H) # HEAD
# Obtain list of patches to be processed
pick_list=$(git rev-list --first-parent ${last_commit}..${target} --reverse)
# Exit successfully if there's nothing to be done
if [ -z "$pick_list" ] ; then
print_blue "Great news - we're up-to-date\n"
exit 0
fi
print_blue "Patches to process from ${target}:"
tput rmam # Don't wrap
git --no-pager log --first-parent --format="%C(auto) %h %s" ${last_commit}..${target} --reverse
tput smam # Reset wrapping
echo
}
function rebase_no_fail
{
local args=${@}
git rebase ${args} && true
while [[ -f ${DOTGIT}/REBASE_HEAD ]]; do
print_red "\nRebase needs attention\n"
print_blue "Either use another shell or Ctrl+z this one to fix, then \`fg\` and hit return"
read
if git rebase --show-current-patch 2>&1 | grep -q "No rebase in progress"; then
print_blue "Rebase no longer in progress - assuming the issue was rectified"
else
git rebase --continue && true
fi
done
echo
}
function commit_to_common_patches()
{
local commit=${1}
print_blue "Committing all changes into 'common-patches' repo\n"
git -C patches/ add .
git -C patches commit -s -F- <<EOF
$target: update series${commit_additional_text}
up to $(git --no-pager show -s --pretty='format:%h ("%s")' ${commit})
EOF
echo
}
function create_series_and_patch_files()
{
up_to=${1}
branch=${target#*/}
if [ ! -z "${UNTESTABLE}" ]; then
status="Untested"
else
status="Tested"
fi
rm patches/*.patch
echo "# Android Series" > patches/series
cat <<EOT > patches/series
#
# $branch patches
#
# Applies onto upstream $(git log -1 --format=%h $base_commit) Linux $(git describe $base_commit)
# Matches $branch $(git show -s --pretty='format:%h ("%s")' $up_to)
# Status: ${status}
#
EOT
files=() # Keep track of used *.patch file names to deal with collisions
print_blue "Updating 'series' file\n"
for sha1 in $(git rev-list ${base_commit}.. --reverse); do
# Create the *.patch filename
patchfilename=$(git show -s --format=%f ${sha1})
printf -v patch_file "%s.patch" ${patchfilename}
# Identify and work around collisions (<name>-<index>.patch)
index=1
while [[ " ${files[@]} " =~ " ${patch_file} " ]]; do
((index++))
printf -v patch_file "%s-%d.patch" ${patchfilename} ${index}
done
files+=(${patch_file})
# Write the actual patch file and update the series file
git format-patch ${sha1} -1 \
--no-signoff \
--keep-subject \
--zero-commit \
--no-signature \
--stdout > patches/${patch_file}
# Remove 'index' changes - they are not required and clog up the diff
sed -i '/index [0-9a-f]\{12,\}\.\.[0-9a-f]\{12,\} [0-9]\{6\}/d' patches/$patch_file
sed -i '/index [0-9a-f]\{12,\}\.\.[0-9a-f]\{12,\}$/d' patches/$patch_file
print_blue "Adding ${patch_file}"
echo ${patch_file} >> patches/series
done
echo
}
commit_patches () {
local commit=${1}
if [ -z "${UNTESTABLE}" ]; then
while ! git --no-pager diff ${commit} --exit-code > /dev/null; do
print_red "Failed to commit patches: Unexpected diff found:\n"
git --no-pager diff ${commit}
echo
print_blue "Either use another shell or Ctrl+z this one to fix, then \`fg\` and hit return"
read
done
else
print_red "WARNING: Committing to Common Patches without testing\n"
sleep 2
fi
if ! is_merge_commit ${commit}; then
print_blue "Entering interactive rebase to rearrange (press return to continue or Ctrl+c to exit)"
read
rebase_no_fail -i ${base_commit}
fi
create_series_and_patch_files ${commit}
commit_to_common_patches ${commit}
# Read new (base, last and target) variables
read_series_file
LOCAL_LAST_COMMIT=$(git --no-pager log -n1 --format=%H) # HEAD
}
function process_merge_commit()
{
local commit=${1}
# Here ${commit} is the Android merge commit and ${parent_commit} is the upstream one
parent_commit=$(git show --no-patch --format="%p" ${commit} | awk '{print $2}')
parent_commit_desc=$(git describe $parent_commit)
UNTESTABLE=""
if ! git show ${parent_commit} --no-patch --format=%an | grep -q "Linus Torvalds"; then
print_red "WARNING: Skipping non-Torvalds merge\n"
UNTESTABLE=true
return
fi
base_commit=${parent_commit}
print_blue "Found merge (new base) commit - rebasing onto upstream commit ${parent_commit_desc}\n"
rebase_no_fail ${base_commit}
commit_additional_text=" (rebase onto ${parent_commit_desc})"
commit_patches ${commit}
}
function process_normal_commit()
{
local commit=${1}
print_blue "Applying: "
if ! git cherry-pick ${commit} --exit-code; then
print_red "Failed to check-pick commit: $(git log --oneline -n1 ${commit})\n"
print_blue "Either use another shell or Ctrl+z this one to fix, then \`fg\` and hit return"
read
fi
echo
}
function process_pick_list()
{
local commit=${1}
# Handle merge commit - ${target} has a new merge-base
if is_merge_commit ${commit}; then
# Apply any regular patches already processed before this merge
if [ "$(git log ${LOCAL_LAST_COMMIT}..HEAD)" != "" ]; then
commit_additional_text=""
commit_patches ${commit}~1 # This specifies the last 'normal commit' before the 'merge commit'
fi
process_merge_commit ${commit}
return
fi
process_normal_commit ${commit}
}
function sync_with_target()
{
preamble # Start-up checks and initialisation
if git diff ${last_commit}..${target} --exit-code > /dev/null; then
oneline=$(git show -s --pretty='format:%h ("%s")' ${target})
print_red "No meaningful work to be done - was all the work reverted?\n"
print_blue "Processing anyway - don't forget to remove them (press return to continue)"
read
fi
for commit in ${pick_list}; do
process_pick_list ${commit}
done
# Apply any regular patches already processed
if [ "$(git log ${LOCAL_LAST_COMMIT}..HEAD)" != "" ]; then
commit_additional_text=""
commit_patches ${commit} # Commit up to last 'normal commit' processed
fi
print_blue "That's it, all done!\n"
exit 0
}
function start()
{
sanity_check
read_series_file
if [ ! -z ${REFRESH} ]; then
local commit=$(git --no-pager log -n1 --format=%H ${last_commit}) # HEAD
print_blue "Refreshing Quilt patches\n"
create_series_and_patch_files ${commit}
print_red "Quilt patches refreshed - please check and commit the result\n"
if [ ! -z ${ONLYREFRESH} ]; then
exit 0
else
print_blue "Once result has been committed, press return to continue syncing or Ctrl+x to exit\n"
read
fi
fi
sync_with_target
}
while [ $# -gt 0 ]; do
case "$1" in
--local)
LOCAL=true
;;
--refresh|--refresh-patches)
REFRESH=true
;;
--only-refresh|--only-refresh-patches)
REFRESH=true
ONLYREFRESH=true
;;
*)
echo "Unknown argument: ${1}"
usage
esac
shift
done
start