Revision control

++ #!/bin/bash
++ # Copyright (C) 2024 The Qt Company Ltd.
++ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ usage () {
++ echo Usage: `basename $0` "[-h|-u] [-d doclet] [-j javadoc] [-p user] [-s sdk] [-t task] [--] prior soon";
++ }
++
++ help () {
++ usage
++ cat <<EOF
++
++ Prepare commits for pushing to Gerrit, two commits for each Android Java package
++ within the git module, that express the interesting changes in API between a prior
++ release (tag or branch) and an imminent release (branch).
++
++ The script looks for various Java sources under the qt5 super repo path and generates
++ API descriptions for specific package names provided here, these are pre-defined Qt
++ for Android package names. This process is done for the prior Qt release and it's
++ committed as a base to-be-ignored commit. The same is done for Qt release being
++ reviewed and a commit is created on top.
++
++ The script can push commits to Gerrit, and automatically abondon the base commits.
++ Also, it can update existing API review patches, given that the local git branch
++ that was used to push before still exists with valid commits.
++
++ Optionally a Jira task number can be provided.
++
++ -h
++ --help Print this help and exit.
++
++ -p USER
++ --push-as USER
++ Push the review commits to Gerrit under the provided username.
++ If this is not provided, commits won't be pushed.
++
++ -s PATH
++ --platform-jar PATH
++ The Android SDK platform JAR that's used to compile the Java
++ source code. This is typically the same as the maximum
++ supported Android version for a given Qt release.
++
++ -t ID
++ --task ID
++ --task-number ID
++ Include a Task-number: in the commit message, with the
++ given id, ID, of the bug-tracker issue being used to keep
++ track of the API change review.
++ -u
++ --usage Print simple usage summary (first line above) and exit.
++
++ -- End option processing.
++
++ Arguments:
++
++ prior The commit (branch, tag or sha1) of a prior release.
++ soon The branch approaching release.
++
++ EOF
++ }
++
++ warn () { echo "$@" >&2; }
++ die () { warn "$@"; exit 1; }
++ second () {
++ if [ $# -lt 2 ]
++ then die "No argument supplied for $1"
++ elif [ -z "$2" ]
++ then die "Empty argument passed for $1 "
++ fi
++ echo "$2"
++ }
++
++ PLATFORM_JAR=
++ GERRIT_USER=
++ TASK_NUMBER=
++
++ bad () { usage >&2; die "$@"; }
++ while [ $# -gt 0 ]
++ do case "$1" in
++ -u|--usage) usage; exit 0 ;;
++ -h|--help) help; exit 0 ;;
++ -p|--push-as) GERRIT_USER=`second "$@"`; shift 2 ;;
++ -s|--platform-jar) PLATFORM_JAR=`second "$@"`; shift 2 ;;
++ -t|--task|--task-number) TASK_NUMBER=`second "$@"`; shift 2 ;;
++ --) shift; break ;;
++ -*) bad "Unrecognised option: $1" ;;
++ *) break ;;
++ esac
++ done
++
++ # Check basic expectations of context:
++ [ -f init-repository ] || \
++ die "I expect to be run in the top level directory of the qt5 module (see --help)."
++ QT_SUPER_REPO="$(pwd)"
++
++ DOCLET_PATH=$QT_SUPER_REPO/qtqa/scripts/api-review/src
++
++ REVIEW_FILENAME=qt-java-api-signature.txt
++
++ # Select revisions to compare:
++ [ $# -eq 2 ] || bad "Expected exactly two arguments, got $#: $@"
++ for arg
++ do git rev-parse "$arg" -- >/dev/null || bad "Failed to parse $arg as a git ref"
++ done
++ PRIOR="$1"
++ RELEASE="$2"
++
++ check_file_param() {
++ [ -n "$1" ] || die "$2 not provided (see --help)."
++ [ -f "$1" ] || die "$2 path '$1' doesn't exist."
++ }
++
++ check_file_param "$PLATFORM_JAR" "Android Platform JAR"
++ command -v javac &> /dev/null || die "javac command not found (set JAVA_HOME)."
++ command -v javadoc &> /dev/null || die "javadoc command not found (set JAVA_HOME)."
++
++ compile_doclet() {
++ javac $DOCLET_PATH/org/qtproject/qt/android/JavaApiSignature.java
++ }
++
++ run_javadoc() {
++ source_paths="$1"
++ version="$2"
++ output_dir="$3"
++ shift 3
++ java_packages=("${@#*:}")
++
++ echo $output_dir
++ pushd $output_dir
++
++ javadoc \
++ -doclet org.qtproject.qt.android.JavaApiSignature \
++ -docletpath $DOCLET_PATH \
++ --output ${REVIEW_FILENAME} \
++ --class-path $PLATFORM_JAR \
++ -sourcepath $source_paths \
++ "${java_packages[@]}" 2>&1 1>/dev/null | \
++ # Ignore the five-line warning starting with this is line.
++ sed '/javadoc: warning - The old Doclet and Taglet APIs/{N;N;N;N;d;}'
++
++ mv $REVIEW_FILENAME $version-$REVIEW_FILENAME
++
++ popd
++ }
++
++ run_javadoc_for_all_repos() {
++ version="$1"
++ repos=("${@:2}")
++
++ java_source_paths=$(
++ find "${repos[@]}" -type d -path '*/src/*/org/qtproject/qt/android' -exec echo {} + | \
++ sed 's/\/org\/qtproject\/qt\/android[^ ]*//g' | sed 's/:$//' | tr ' ' ':'
++ )
++
++ echo "############################################"
++ printf "#### Generating JAVADOC for %-8s ####\n" $version
++ echo "############################################"
++ for index in "${!repo_packages[@]}"; do
++ entry="${repo_packages[$index]}"
++ repo="${entry%%:*}"
++ packages="${entry#*:}"
++ run_javadoc $java_source_paths $version "$QT_SUPER_REPO/$repo" $packages
++ done
++ echo
++ }
++
++ checkout_git_repos() {
++ branch="$1"
++ repos=("${@:2}")
++
++ echo "Checking out $branch in Qt repositories"
++ for repo in "${repos[@]}"; do
++ echo "Repo: $repo"
++ git -C "$repo" fetch
++ git -C "$repo" checkout -q $branch
++ done
++ echo
++ }
++
++ commit_api_verion() {
++ message="$1"
++ body="$2"
++ ticket="$3"
++ change_id="$4"
++ version="$5"
++
++ success=0
++ head_commit_count="$(git rev-list --count HEAD...$version)"
++ if [[ "$(ls -A $version-$REVIEW_FILENAME)" || $head_commit_count -eq 1 ]]; then
++ mv $version-$REVIEW_FILENAME $REVIEW_FILENAME
++
++ if [ "$(git status | grep $REVIEW_FILENAME)" ]; then
++ echo "Committing API for $version..."
++ git add $REVIEW_FILENAME
++ if [[ -n $ticket ]]; then
++ footer=$(echo -e "Task-number: $ticket\n$change_id")
++ git commit -q -m "$message" -m "$body" -m "$footer"
++ else
++ git commit -q -m "$message" -m "$body" -m "$change_id"
++ fi
++ success="$?"
++ else
++ echo "No API changes to commit for $version..."
++ git reset HEAD^
++ rm $REVIEW_FILENAME
++ success="-1"
++ fi
++ else
++ echo "No API files for $version!"
++ rm $version-$REVIEW_FILENAME
++ fi
++
++ return $success
++ }
++
++ commit_diff() {
++ repo="$1"
++
++ # Checkout review branch, remove it if it already exists
++ echo "Preparing API Review Diff for $repo"
++ cd "$repo"
++ review_branch_name="java_api_review_${RELEASE}"
++
++ # Get previous Change-Id if a review branch was already created
++ if [ -n "$(git branch --list | grep -w $review_branch_name)" ]; then
++ change_ids_log=$(git log --reverse "$RELEASE..$review_branch_name" | \
++ grep "Change-Id: " | sed 's/^[ \t]*//')
++ while IFS= read -r line; do change_ids+=("$line"); done <<< "$change_ids_log"
++ fi
++
++ echo "Checking out review branch..."
++ git checkout -q $RELEASE
++ git branch -q -D $review_branch_name
++ git checkout -q -b $review_branch_name $RELEASE
++
++ if [ -f "$REVIEW_FILENAME" ]; then
++ rm $REVIEW_FILENAME
++ fi
++
++ module=$(basename $repo)
++ commit_msg="WIP: [Ignore][API Base] Review $module $RELEASE Android Java API"
++ body="Auto-generated $PRIOR baseline commit for reviewing $RELEASE, please ignore."
++ if [[ -n "${change_ids[1]}" ]]; then
++ prior_change_id="${change_ids[0]}"
++ release_change_id="${change_ids[1]}"
++ else
++ prior_change_id=""
++ release_change_id="${change_ids[0]}"
++ fi
++
++ commit_api_verion "$commit_msg" "$body" "" "$prior_change_id" "$PRIOR"
++
++ if [ -d "$review_dir_base" ]; then
++ rm -R $review_dir_base
++ fi
++
++ commit_msg="Review $module $RELEASE Android Java API"
++ body="Auto-generated commit for reviewing the Android Java API of $module."
++ commit_api_verion "$commit_msg" "$body" "$TASK_NUMBER" "$release_change_id" "$RELEASE"
++
++ if [ $? -eq 0 ]; then
++ echo "Check the API diff file under $module/$REVIEW_FILENAME"
++ fi
++ }
++
++ do_gerrit_command() {
++ ssh -p 29418 $GERRIT_USER@codereview.qt-project.org gerrit "$@"
++ }
++
++ push_to_gerrit() {
++ version="$1"
++
++ echo "Pushing changes to Gerrit..."
++ git push gerrit HEAD:refs/for/$version
++
++ # Set diff review patch topic
++ do_gerrit_command set-topic $(git log -n 1 --skip=0 --pretty=format:%H) \
++ --topic "java_api_review_$version"
++ }
++
++ is_valid_repo_dir() {
++ local dir="$1"
++
++ [ -d "$dir" ] || return 1 # must be an existing dir
++ [ "$(ls -A "$dir" 2>/dev/null)" ] || return 1 # must not be empty
++ git -C "$dir" rev-parse --is-inside-work-tree >/dev/null 2>&1 || return 1 # must be a git dir
++
++ return 0
++ }
++
++ # Individual key-value assignments for the associative array
++ repo_packages_map=(
++ "qtbase:org.qtproject.qt.android \
++ org.qtproject.qt.android.bindings \
++ org.qtproject.qt.android.extras \
++ org.qtproject.qt.android.network \
++ org.qtproject.qt.android.networkinformation"
++ "qtconnectivity:org.qtproject.qt.android.bluetooth \
++ org.qtproject.qt.android.nfc"
++ "qtdeclarative:org.qtproject.qt.android"
++ "qtmultimedia:org.qtproject.qt.android.multimedia"
++ "qtpositioning:org.qtproject.qt.android.positioning"
++ "qtspeech:org.qtproject.qt.android.speech"
++ "qtwebview:org.qtproject.qt.android.view"
++ )
++
++ # Get list of absolute paths for git repos
++ repos=()
++ repo_packages=()
++ for entry in "${repo_packages_map[@]}"; do
++ repo="${entry%%:*}"
++ dir="$QT_SUPER_REPO/$repo"
++
++ if is_valid_repo_dir "$dir"; then
++ repos+=("$dir")
++ repo_packages+=("$entry")
++ else
++ echo "Skipping '$dir': not a valid git repo" >&2
++ fi
++ done
++
++ [[ ${#repos[@]} -eq 0 ]] && die "No valid git repositories found, nothing to do here."
++
++ compile_doclet
++
++ # Generate API for prior version
++ checkout_git_repos "$PRIOR" "${repos[@]}"
++ run_javadoc_for_all_repos $PRIOR "${repos[@]}"
++
++ # Generate API for release version
++ checkout_git_repos "$RELEASE" "${repos[@]}"
++ run_javadoc_for_all_repos $RELEASE "${repos[@]}"
++
++ # Commit diffs and push
++ for repo in "${repos[@]}"; do
++ commit_diff $repo
++
++ commit_count="$(git rev-list --count HEAD...$RELEASE)"
++ if [[ $commit_count -gt 0 ]] && [[ -n $GERRIT_USER ]]; then
++ if [ $commit_count -eq 2 ]; then
++ # Restore base API commit if it exists before pushing
++ echo "Restore base patch if exists"
++ base_commit_change_id=$(git show --summary HEAD~1 | grep "Change-Id: " | \
++ sed 's/^[ \t]*//' | cut -d' ' -f2-)
++ do_gerrit_command review --restore $base_commit_change_id
++ fi
++
++ push_to_gerrit $RELEASE
++
++ if [ $commit_count -eq 2 ]; then
++ # Abandon base API commit if it exists
++ base_commit_commit_id=$(git log -n 1 --skip=1 --pretty=format:%H)
++ echo "Abandoning remote commit $base_commit_commit_id..."
++ do_gerrit_command review --abandon $base_commit_commit_id
++ fi
++ fi
++ echo
++ done