Newer
Older
#
# dim - drm inglorious maintainer script
#
# Copyright © 2012-2018 Intel Corporation
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice (including the next
# paragraph) shall be included in all copies or substantial portions of the
# Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#
# Authors:
# Daniel Vetter <daniel.vetter@ffwll.ch>
# Jani Nikula <jani.nikula@intel.com>
# User configuration. Global DIM_ prefixed uppercase variables. Set in
# environment or configuration file. See dimrc.sample for an example.
#
# dim configuration file
DIM_CONFIG=${DIM_CONFIG:-$HOME/.dimrc}
if [ -r $DIM_CONFIG ]; then
# shellcheck source=/dev/null
. $DIM_CONFIG
fi
# prefix for repo directories
DIM_PREFIX=${DIM_PREFIX:-$HOME/linux}
# main maintainer repo under $DIM_PREFIX
# mail user agent. must support a subset of mutt(1) command line options:
# usage: $DIM_MUA [-s subject] [-i file] [-c cc-addr] to-addr [...]
DIM_MUA=${DIM_MUA:-mutt}
# make options (not used for C=1)
DIM_MAKE_OPTIONS=${DIM_MAKE_OPTIONS:--j20}
Ander Conselvan de Oliveira
committed
# command to run after dim apply
DIM_POST_APPLY_ACTION=${DIM_POST_APPLY_ACTION:-}
# greetings pull request template
DIM_TEMPLATE_HELLO=${DIM_TEMPLATE_HELLO:-$HOME/.dim.template.hello}
# signature pull request template
DIM_TEMPLATE_SIGNATURE=${DIM_TEMPLATE_SIGNATURE:-$HOME/.dim.template.signature}
# GPG key id for signing tags. If unset, don't sign.
DIM_GPG_KEYID=${DIM_GPG_KEYID:+-u $DIM_GPG_KEYID}
# Internal configuration. Global dim_ prefixed variables.
dim=$(basename $0)
dim_timestamp="$(date --utc +%Yy-%mm-%dd-%Hh-%Mm-%Ss) UTC"
maintainer_tools_https=https://gitlab.freedesktop.org/drm/maintainer-tools.git
# Recipients for all dim based pull requests.
# Add To: lines to the end, Cc: lines in the beginning with -c.
dim_pull_request_recipients=(
-c "Jani Nikula <jani.nikula@linux.intel.com>"
-c "Joonas Lahtinen <joonas.lahtinen@linux.intel.com>"
-c "Rodrigo Vivi <rodrigo.vivi@intel.com>"
-c "Maarten Lankhorst <maarten.lankhorst@linux.intel.com>"
-c "Maxime Ripard <maxime.ripard@bootlin.com>"
-c "dri-devel@lists.freedesktop.org"
-c "intel-gfx@lists.freedesktop.org"
-c "dim-tools@lists.freedesktop.org"
"Dave Airlie <airlied@gmail.com>"
"Daniel Vetter <daniel.vetter@ffwll.ch>"
dim_integration_config=nightly.conf
#
# Only function and alias definitions until the command line argument parsing
# and subcommand handling at the end.
#
function read_integration_config
{
# clear everything first to allow configuration reload
unset drm_tip_repos drm_tip_config
declare -g -A drm_tip_repos
declare -g -a drm_tip_config
if [ -r $DIM_PREFIX/drm-rerere/$dim_integration_config ]; then
# shellcheck source=/dev/null
source $DIM_PREFIX/drm-rerere/$dim_integration_config
if [[ "${#drm_tip_repos[@]}" = "0" ]] || [[ "${#drm_tip_config[@]}" = "0" ]]; then
echoerr "$dim_integration_config not set up correctly, please fix manually"
echoerr "$dim_integration_config is missing, please check your configuration and/or run dim setup"
fi
dim_branches=
for conf in "${drm_tip_config[@]}"; do
local repo branch override
if [[ "$repo" = "drm-intel" || "$repo" = "drm-misc" ]]; then
dim_branches="$dim_branches $branch"
fi
done
}
function warn_or_fail
{
if [[ $FORCE ]] ; then
echoerr "WARNING: $1, but continuing"
elif [[ $DRY ]] ; then
echoerr "WARNING: $1, but continuing dry-run"
echoerr "ERROR: $1, aborting"
function pause
{
read -rsp "Press any key to continue..." -n1 key2
echo
}
function ask_user
{
local prompt="$@ (y/N) "
read -n 1 -rsp "$prompt"
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
return 0
else
return 1
fi
}
#
# Variable naming convetion:
#
# repo:
# symbolic git repository name from $dim_integration_config
# remote:
# local remote name in the git repository for the current path
# branch:
# git branch name - dim assumes that the remote and local name match
# url:
# url to a repo, using ssh:// protocol
# git_url:
# url to a repo, but using anonymous git:// protocol
#
# The below functions map between these.
#
function url_to_remote # url [url ...]
local url remote
if [[ "$#" = "0" ]]; then
echoerr "url_to_remote without URLs"
return 1
for url; do
remote=$(git remote -v | grep -m 1 "$url/\? (" | cut -f 1)
if [[ -n "$remote" ]]; then
echo "$remote"
return 0
fi
done
echoerr "No git remote for any of the URLs $* found in $(pwd)"
url=$1
remote=${url%.git}
remote=${remote##*/}
read -r -i "$remote" -e -p "Enter a name to auto-add this remote, leave blank to abort: " remote
if [[ -z "$remote" ]]; then
echoerr "Please set it up yourself using:"
echoerr " $ git remote add <name> $url"
echoerr "with a name of your choice."
git remote add $remote $url
echo $remote
return 0
function pick_protocol_url # (git|ssh|https|whatever) url [url ...]
{
local url protocol protocol_url
if [[ "$#" -lt "2" ]]; then
echoerr "pick_protocol_url without protocol or URLs"
return 1
fi
protocol=$1
shift
# Find the URL that has given protocol
for url; do
case $url in
${protocol}://*)
protocol_url=$url
break
;;
esac
done
if [[ -z "$protocol_url" ]]; then
echoerr "No $protocol URL in any of the URLs: $*"
return 1
fi
echo $protocol_url
}
function branch_to_remote # branch
{
repo=$(branch_to_repo $branch)
if [[ -z "$repo" ]] ; then
# fallback for special branches like rerere-cache
remote=$(git rev-parse --abbrev-ref --symbolic-full-name "$branch@{upstream}")
remote=${remote%%/*}
else
remote=$(repo_to_remote $repo)
fi
local repo url_list
repo=$1
url_list=${drm_tip_repos[$repo]}
if [[ -z "$url_list" ]]; then
echoerr "unknown repo $repo"
return 1
fi
url_to_remote $url_list
function branch_to_repo # branch
{
for conf in "${drm_tip_config[@]}"; do
local repo branch override
if [[ "$branch" == "$1" ]] ; then
echo $repo
fi
done
echo ""
}
local using
using="${BASH_SOURCE[0]}"
if [[ ! -e "$using" ]]; then
echoerr "could not figure out the version being used ($using)."
fi
if [[ ! -e "$DIM_PREFIX/maintainer-tools/.git" ]]; then
echoerr "could not find the upstream repo for $dim."
git --git-dir=$DIM_PREFIX/maintainer-tools/.git fetch -q
if ! git --git-dir=$DIM_PREFIX/maintainer-tools/.git show "@{upstream}:dim" |\
diff "$using" - >& /dev/null; then
echoerr "not running upstream version of the script."
function git_fetch_helper # remote
{
local remote
remote=$1
# old git versions returned 128 if there was nothing to fetch
if [[ $? -ne "128" ]] ; then
echoerr "Failed to fetch $remote"
return 1
fi
fi
}
function git_current_branch
{
git symbolic-ref -q --short HEAD
}
function git_is_current_branch # branch
{
test "$(git_current_branch)" = "$1"
function git_branch_exists # branch
{
if [[ "$(git branch --list $1)" == "" ]] ; then
false
else
true
fi
}
# $1: branch
# $2: upstream
function git_unmerged_tags
{
local branch upstream
branch=$1
upstream=$2
# assume branch based tag names, ensure space separated list
git log --decorate --pretty=%D "$branch@{upstream}" ^$upstream |\
grep -o "tag: $branch-[0-9-]\+" |\
sed -e "s/^tag: //" |\
tr "\n" " "
}
function git_committer_email
{
if ! committer_email=$(git config --get user.email) ; then
fi
echo $committer_email
}
function check_for_updates
{
local stamp stampfile
stampfile=$HOME/.dim-update-check-timestamp
# daily check for updates based on file timestamp
stamp=$(stat --printf=%Y $stampfile 2>/dev/null || echo -n 0)
if [[ $((stamp + 24*60*60)) -lt $(date +%s) ]]; then
dim_uptodate || true
touch $stampfile
fi
}
function check_git_version
{
local min_version="git version 2.8"
if ! echo -e "$min_version\n$(git version)" | sort -VC; then
echoerr "WARNING: recommended minimum $min_version, you have $(git version)"
fi
}
function check_dim_config
{
if [[ "$DIM_REPO" == "drm-tip" || "$DIM_REPO" == "drm-rerere" || "$DIM_REPO" == "maintainer-tools" ]]; then
echoerr "WARNING: setting $DIM_REPO for DIM_REPO not allowed"
exit 1
fi
}
# get message id from file
# $1 = file
message_get_id ()
{
python <<EOF
from email.parser import Parser
headers = Parser().parse(open('$1', 'r'))
message_id = headers['message-id']
if message_id is not None:
print(message_id.strip('<>'))
EOF
}
message_print_body ()
{
python2 <<EOF
import email
def print_msg(file):
msg = email.message_from_file(file)
for part in msg.walk():
if part.get_content_type() == 'text/plain':
print(part.get_payload(decode=True))
print_msg(open('$1', 'r'))
EOF
}
# append all arguments as tags at the end of the commit message of HEAD
function dim_commit_add_tag
for arg; do
# the first sed deletes all trailing blank lines at the end
git log -1 --pretty=%B | \
sed -e :a -e '/^\n*$/{$d;N;ba' -e '}' | \
sed "\$a${arg}" | \
git commit --amend -F-
done
# $1: branch [optional]
function git_find_tip
{
git log $1 -1 --format=%H --grep="^drm-tip: .* integration manifest$"
}
# $1: branch [optional]
function dim_retip
{
local branch upstream new_upstream
if [ -n "$branch" ] ; then
shift
else
branch=$(git symbolic-ref --short HEAD)
fi
if repo_to_remote drm-tip &> /dev/null ; then
new_upstream=$(repo_to_remote drm-tip)/drm-tip
else
new_upstream=$(git branch -r | grep '\/drm-tip$')
upstream=$(git_find_tip "$branch")
if [[ -z "$upstream" ]]; then
echoerr "$branch is not based on drm-tip"
return 1
fi
git rebase --onto $new_upstream $upstream $branch "$@"
function dim_range_diff
{
local branch
branch=${1:-@\{1\}}
if [[ $(git rev-parse $branch | wc -l) -eq 1 ]] ; then
if [[ $(git rev-parse $branch) == "$branch" ]] ; then
branch="@{1}"
else
shift || true
fi
git range-diff $branch...HEAD "$@"
else
git range-diff "$@"
fi
}
# update for-linux-next and for-linux-next-fixes branches
function update_linux_next # branch next next-fixes fixes
local branch linux_next linux_next_fixes linux_fixes repo remote
branch=$1
linux_next=$2
linux_next_fixes=$3
linux_fixes=$4
if [[ $repo != $(branch_to_repo $linux_next) ]] ; then
# always update drm-intel-fixes
echo -n "Pushing $linux_fixes to for-linux-next-fixes... "
git push $DRY_RUN $remote +$remote/$linux_fixes:for-linux-next-fixes # >& /dev/null
if git merge-base --is-ancestor $remote/$linux_next_fixes $remote/$linux_fixes ; then
# -fixes has caught up to dinf, i.e. we're out of the merge
# window. Push the next queue.
echo -n "Out of merge window. Pushing $linux_next to for-linux-next... "
git push $DRY_RUN $remote +$remote/$linux_next:for-linux-next >& /dev/null
# dinf is ahead of -fixes, i.e. drm-next has already closed for
# the next merge window and we've started to gather new fixes
# for the current -next cycle. Push dinf
echo -n "Pushing $linux_next_fixes to for-linux-next... "
git push $DRY_RUN $remote +$remote/$linux_next_fixes:for-linux-next >& /dev/null
function check_conflicts # tree
if git diff | grep -q '\(<<<<<<<\|=======\|>>>>>>>\||||||||\)' ; then
# we need an empty line to make it look pretty
echoerr ""
echoerr "FAILURE: Could not merge $1"
git -C ${1:-$PWD} rev-parse --absolute-git-dir
if ! git_is_current_branch rerere-cache; then
echo "Fail: Branch setup for the rerere-cache is borked."
exit 1
fi
if ! git pull -q ; then
# We raced with someone else hitting the same conflict, or the
# erratic git gc for rr-cache entries nuke a few entries we
# still want to keep. Clean up and try, but only when the
# initial pull fails since otherwise there's no way to keep new
# resolutions around.
echo "Conflict in the rr-cache, cleaning up"
git clean -fdx rr-cache/
git checkout -f ':(glob)rr-cache/**'
if ! git pull -q ; then
echoerr "Failed to update the rerere cache."
echoerr "Please manually run"
echoerr " $ cd $DIM_PREFIX/drm-rerere ; git pull"
echoerr "and fixup any issues."
cd - > /dev/null
}
function update_rerere_cache
{
echo -n "Updating rerere cache... "
pull_rerere_cache
cd $DIM_PREFIX/drm-tip/
rr_cache_dir=$(git rev-parse --git-common-dir)/rr-cache
if [ ! -L $rr_cache_dir ] ; then
if [ -d $rr_cache_dir ] ; then
rm -Rf $rr_cache_dir
ln -s "$DIM_PREFIX/drm-rerere/rr-cache" $rr_cache_dir
echo "Done."
}
function commit_rerere_cache
{
local remote file commit_message
cd $DIM_PREFIX/drm-rerere/
remote=$(branch_to_remote rerere-cache)
git add ./*.patch >& /dev/null || true
git add fixups/*.patch >& /dev/null || true
for file in $(git ls-files -- rr-cache); do
if ! git log --since="60 days ago" --name-only -- $file | grep $file &> /dev/null; then
git rm $file &> /dev/null || true
done
find rr-cache/ -mtime -1 -type f -not -name "thisimage*" -print0 | xargs -0 git add > /dev/null || true
git rm rr-cache/rr-cache &> /dev/null || true
commit_message=$(mktemp)
cat > $commit_message <<-EOF
$dim_timestamp: $integration_branch rerere cache update
$(git --version)
EOF
if git commit -F $commit_message >& /dev/null; then
echo -n "Pushing rerere cache... "
git push $DRY_RUN $remote HEAD >& /dev/null && echo "Done."
function fetch_all
{
for repo in "${!drm_tip_repos[@]}"; do
remote=$(repo_to_remote $repo)
if [[ "$repo" = "$remote" ]]; then
echo -n "Fetching $repo... "
else
echo -n "Fetching $repo (local remote $remote)... "
fi
git_fetch_helper $remote
echo "Done."
done
}
function find_fixup_file # repo branch
{
local file_paths repo branch rerere
repo=$1
branch=$2
rerere=$DIM_PREFIX/drm-rerere
file_paths="$rerere/${repo}-${branch//\//-}-fixup.patch
$rerere/fixups/${branch//\//-}.patch"
for file_path in $file_paths; do
[ -f "$file_path" ] && break
done
echo "$file_path"
}
local integration_branch specfile first rerere repo remote
integration_branch=drm-tip
first=1
rerere=$DIM_PREFIX/drm-rerere
if git status --porcelain | grep -v "^.. rr-cache" | grep -q -v "^[ ?][ ?]"; then
warn_or_fail "integration configuration file $dim_integration_config not committed"
echo -n "Reloading $dim_integration_config... "
read_integration_config
cd $DIM_PREFIX/$integration_branch
if ! git_is_current_branch $integration_branch ; then
echo "Branch setup for the integration repo is borked"
exit 1
fi
# rerere uses sha1 of the diff to identify conflicts, we must ensure
# that they look the same for everyone
git config merge.conflictstyle merge
for conf in "${drm_tip_config[@]}"; do
local branch override sha1 fixup_file
remote=$(repo_to_remote $repo)
sha1=$remote/$branch
if [[ "$repo" = "$remote" ]]; then
echo -n "Merging $repo/$branch... "
else
echo -n "Merging $repo/$branch (local remote $remote)... "
fi
if [[ -n "$override" ]]; then
sha1=$override
echo -n "Using override sha1: $sha1... "
git reset --hard $sha1 &> /dev/null
elif git merge --rerere-autoupdate --ff-only $sha1 >& /dev/null ; then
# nothing to do if just fast-forward
fixup_file=$(find_fixup_file $repo $branch)
echo $branch > .fixup_branch
git merge --rerere-autoupdate --no-commit $sha1 >& /dev/null || true
# normalize conflict markers
if git grep -l '^>>>>>>> ' &> /dev/null ; then
git grep -l '^>>>>>>> ' | xargs sed -e "s|^>>>>>>> .*$|>>>>>>> $repo\/$branch|" -i
echo -n "Applying manual fixup patch for $integration_branch merge... "
if ! check_conflicts "$repo/$branch" ; then
echoerr "See the section \"Resolving Conflicts when Rebuilding drm-tip\""
echoerr "in the drm-tip.rst documentation for how to handle this situation."
return 1
fi
# because we filter out fast-forward merges there will
# always be something to commit
git commit --no-edit --quiet
echo "Done."
echo -e "$repo $branch $(git rev-parse $sha1)\n\t$(git log -1 $sha1 --pretty=format:%s)" >> $specfile
echo -n "Adding integration manifest $integration_branch: $dim_timestamp... "
mv $specfile integration-manifest
git add integration-manifest
git commit --quiet -m "$integration_branch: $dim_timestamp integration manifest"
remote=$(repo_to_remote drm-tip)
echo -n "Pushing $integration_branch... "
git push $DRY_RUN $remote +HEAD >& /dev/null && echo "Done."
# additional patch checks before pushing, e.g. for r-b tags
function checkpatch_commit_push
{
local sha1 managed_branch rv author committer author_outlook cite
cite=$(dim_cite $sha1)
# use real names for people with many different email addresses
author=$(git show -s $sha1 --format="format:%an")
committer=$(git show -s $sha1 --format="format:%cn")
# outlook mangles mails into "Last, First"
author_outlook=$(git show -s $sha1 --format="format:%an" | sed -e 's/\([^ ]*\) \(.*\)/\2, \1/')
# check for author sign-off
if ! git show -s $sha1 | grep -qi "Signed-off-by:.*\\($author\\|$author_outlook\\)" ; then
echoerr "$cite: author Signed-off-by missing."
fi
# check for committer sign-off
if ! git show -s $sha1 | grep -qi "Signed-off-by:.*$committer" ; then
echoerr "$cite: committer Signed-off-by missing."
fi
# check for Link tag
if [[ "$managed_branch" = "1" ]] && ! git show -s $sha1 | grep -qi 'Link:' ; then
echoerr "$cite: Link tag missing."
if ! git show -s $sha1 | grep -qi '\(reviewed\|acked\)\S*-by:' && \
! [[ "$committer" != "$author" ]]; then
echoerr "$cite: mandatory review missing."
return $rv
}
local sha1 managed_branch rv body_text cite
sha1=$1
managed_branch=${2}
rv=0
cite=$(dim_cite $sha1)
body_text="$(git show $sha1 -s --format="format:%b" | grep -v "^$" | grep -v "^\S*:")"
if [[ -z "$body_text" ]] ; then
echoerr "$cite: merge commit justification missing."
function checkpatch_commit_push_range
{
rv=0
for sha1 in $(git rev-list "$@" --no-merges) ; do
checkpatch_commit_push $sha1 $managed_branch || rv=1
for sha1 in $(git rev-list "$@" --merges) ; do
checkmerge_commit_push $sha1 $managed_branch || rv=1
done
if [ $rv == "1" ] ; then
warn_or_fail "issues in commits detected"
fi
# push branch $1, rebuild drm-tip. the rest of the arguments are passed to git
local branch remote committer_email count
shift
assert_branch $branch
committer_email=$(git_committer_email)
checkpatch_commit_push_range 1 "$branch@{u}..$branch" --first-parent --committer="$committer_email"
# Apart from maintainers pushing merges or rebases, most patches should
# be pushed in small batches.
count=$(git rev-list --count --first-parent "$branch@{u}..$branch")
if [[ $count -gt 10 ]]; then
if ! ask_user "Pushing $count commits. Are you sure?"; then
echoerr "NOTE: Branch not pushed."
return 1
fi
fi
git push $DRY_RUN $remote $branch "$@"
update_linux_next $branch drm-intel-next-queued drm-intel-next-fixes drm-intel-fixes
update_linux_next $branch drm-misc-next drm-misc-next-fixes drm-misc-fixes
update_linux_next $branch drm-amd-next drm-amd-next-fixes drm-amd-fixes
dim_alias_pq=push-queued
function dim_push_queued
{
dim_push_branch drm-intel-next-queued "$@"
}
dim_alias_pnf=push-next-fixes
function dim_push_next_fixes
{
dim_push_branch drm-intel-next-fixes "$@"
}
dim_alias_pf=push-fixes
function dim_push_fixes
{
dim_push_branch drm-intel-fixes "$@"
}
function dim_push
{
dim_push_branch $(git_current_branch) "$@"
}
function apply_patch #patch_file
local patch message_id committer_email patch_from sob rv
message_id=$(message_get_id $patch)
patch_from=$(grep "From:" "$patch" | head -1)
if [[ "$patch_from" != *"$committer_email"* ]] ; then
if ! git am --scissors -3 $sob "$@" $patch ; then
echoerr "ERROR: git apply-mbox failed"
return 1
fi
if [ -n "$message_id" ]; then
dim_commit_add_tag "Link: https://patchwork.freedesktop.org/patch/msgid/$message_id"
echoerr "WARNING: No message-id found in the patch file."
rv=1
if ! checkpatch_commit HEAD branch; then
if ! check_maintainer $branch HEAD; then
rv=1
fi
Ander Conselvan de Oliveira
committed
eval $DRY $DIM_POST_APPLY_ACTION
return $rv
}
# ensure we're on branch $1, and apply patches. the rest of the arguments are
# passed to git am.
dim_alias_ab=apply-branch
dim_alias_sob=apply-branch
function dim_apply_branch
{
local branch file rv
branch=${1:?$usage}
shift
file=$(mktemp)
dir=$(mktemp -d)
assert_branch $branch
assert_repo_clean
cat > $file
git mailsplit -b -o$dir $file > /dev/null
if ! apply_patch $patch "$@"; then