diff options
Diffstat (limited to 'scripts/api-review/api-review-gen')
-rwxr-xr-x | scripts/api-review/api-review-gen | 376 |
1 files changed, 376 insertions, 0 deletions
diff --git a/scripts/api-review/api-review-gen b/scripts/api-review/api-review-gen new file mode 100755 index 00000000..8396b824 --- /dev/null +++ b/scripts/api-review/api-review-gen @@ -0,0 +1,376 @@ +#!/bin/sh +usage () { echo Usage: `basename $0` "[-h|-u] [-v] [--] prior soon"; } +############################################################################# +## +## Copyright (C) 2016 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the release tools of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 3 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL3 included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 3 requirements +## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 2.0 or (at your option) the GNU General +## Public license version 3 or any later version approved by the KDE Free +## Qt Foundation. The licenses are as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-2.0.html and +## https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +help () { + usage + cat <<EOF + +Prepare a commit for pushing to Gerrit, that expresses the interesting +changes in API between a prior release (tag or branch) and an imminent +release (branch). Certain boring changes are excluded; you can review +them after this has run with git diff -D; if necessary, you can amend +the commit to remove any remaining boredom or include anything +mis-classified as boring, before pushing to Gerrit. + +Run in the top-level directory of a work tree in a clean state; don't +expect it to be left in a clean state. Depends on the dulwich package +for python; see your local package manager or 'pip install dulwich' if +managing python packages with pip. + +Options: + + -u + --usage Print simple usage summary (first line above) and exit. + + -h + --help Print this help and exit. + + -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. + + -a + --amend Amend existing commit rather than extending the review + branch, if it exists already. + + -r + --replace Replace existing branch, if present, re-using its last + Change-Id, rather than updating it. (Conflicts with + --amend, which should be preferred unless these scripts + have changed. If both are used, the last wins.) If + --task-number has not been specified, any Task-number: + footer in the earlier commit will also be preserved. + + -v + --verbose Say what's happening as it happens. + + -q + --quiet Don't mention anything but errors. + + -- End option processing. + +Arguments: + + prior The commit (branch, tag or sha1) of a prior release. + soon The branch approaching release. + +You should see the usual git output describing the commit created. +You should verify that git diff -D is all boring and the commit is all +interesting before pushing this commit to Gerrit. + +Exit status is 1 on error, else 0. Success may mean no change at all, +only boring changes or some interesting changes have been saved. If +there are no changes, the repository is restored to its prior state +(including deleting the api-review-* branch, if created rather than +reused). The other two cases can be distinguished by comparing the +api-review-* branch with the prior release using git diff --quiet; +this succeeds if there is no difference - i.e. if the API change was +all boring so we made no commit. + +After a first pass of review, if prior or soon has changed (and been +fetched), it is possible to rebase the review branch onto prior and +re-run this script to update the review. If you pass the --amend +option and there was a prior commit on the branch, that commit shall +be amended rather than a new commit added to the branch. Otherwise, +any change shall be recorded as a fresh commit - which you can always +squash onto an earlier commit later, after reviewing it, if you prefer +that work-flow so left out --amend. Either way, you can then push the +squashed or amended commit to Gerrit for re-review, as usual. +EOF +} +warn () { echo "$@" >&2; } +die () { warn "$@"; exit 1; } +run () { mutter "Running: $@"; eval "$@" || die "Failed ($?): $@"; } + +# Check basic expectations of context: +if [ -d src -a -f sync.profile ] +then + SYNC='sync.profile' + SRCDIR=src +elif [ -f Source/sync.profile ] # qtwebkit/'s eccentric layout ... +then + SYNC='Source/sync.profile' + SRCDIR=Source +else + die "I expect to be run in the top level directory of a module (see --help)." +fi +THERE=`dirname $0` +[ -n "$THERE" -a -x "$THERE/resetboring.py" ] || \ + die "I don't know where resetboring.py is: please run me via an explicit path." +python -c 'from dulwich.repo import Repo; from dulwich.index import IndexEntry' || \ + die "I need dulwich installed (for resetboring.py; see --help)." +# dulwich 0.16.3 has been known to work; 0.9.4 is too old. + +# Parse command-line: +CHATTY= +AMEND= +bad () { usage >&2; die "$@"; } +while [ $# -gt 0 ] +do case $1 in + -u|--usage) usage; exit 0 ;; + -h|--help) help; exit 0 ;; + -a|--amend) AMEND=--amend; shift ;; + -r|--replace) AMEND=--replace; shift ;; + -t|--task|--task-number) TASK="$2"; shift 2 ;; + -v|--verbose) CHATTY=more; shift ;; + -q|--quiet) CHATTY=less; shift ;; + --) shift; break ;; + -*) bad "Unrecognised option: $1" ;; + *) break ;; + esac +done + +# 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" +RESTORE="`git branch | sed -n -e '/^\* (HEAD/ s/.* \([^ ]*\))$/\1/ p' -e '/^\*/ s/.* // p'`" + +# Implement --verbose, --quiet: +QUIET= +mutter () { true; } +mention () { warn "$@"; } +case "$CHATTY" in + more) mutter () { warn "$@"; } ;; + less) QUIET=-q + mention () { true; } + ;; + *) ;; +esac +retire () { mention "$@"; exit; } +checkout () { run git checkout $QUIET "$@"; } + +# Get API headers of $RELEASE checked out on a branch off $PRIOR: +BRANCH="api-review-$PRIOR-$RELEASE" +mutter "Checking for branch $BRANCH to check out" +case `git branch | grep -wF " $BRANCH" | grep "^[* ] $BRANCH"'$'` in + '') + checkout -b "$BRANCH" "$PRIOR" + NEWBRANCH=yes + if [ -n "$AMEND" ] + then + mention "Ignoring requested $AMEND: no prior $BRANCH" + AMEND= + fi + ;; + '* '*) + case "$AMEND" in + '--replace') + mutter "On prior branch $BRANCH; shall be removed and recreated" + ;; + '--amend') + mutter "Already on branch $BRANCH; preparing to amend it" + ;; + *) + mutter "Already on branch $BRANCH; preparing to extend it" + ;; + esac + ;; + ' '*) + case "$AMEND" in + '--replace') + mutter "Replacing existing branch $BRANCH (reusing its Change-Id)" + ;; + '--amend') + mutter "Reusing existing branch $BRANCH; preparing to amend it" + checkout "$BRANCH" + ;; + *) + mutter "Reusing existing branch $BRANCH; preparing to extend it" + checkout "$BRANCH" + ;; + esac + ;; +esac + +# Implement --replace and --amend: +CHANGEID= +if [ -n "$AMEND" ] +then + # Suppress --amend or --replace unless we have a prior commit on $BRANCH: + if git diff --quiet "$BRANCH" "$PRIOR" + then + if [ -n "$AMEND" ] + then + mention "Suppressing requested $AMEND: no prior commit on $BRANCH" + AMEND= + fi + else # Read (and validate) last commit's Change-Id: + CHANGEID=`git show --summary $BRANCH | sed -ne '/Change-Id:/ s/.*: *//p'` + [ -n "$CHANGEID" ] || die "No prior Change-Id from $BRANCH" + expr "$CHANGEID" : "^I[0-9a-f]\{40\}$" >/dev/null || \ + die "Bad prior Change-Id ($CHANGEID) from $BRANCH" + # Also preserve Task-number, if present and not over-ridden: + [ -n "$TASK" ] || TASK=`git show --summary $BRANCH | sed -ne '/Task-number:/ s/.*: *//p'` + fi +fi +if [ "$AMEND" = '--replace' ] +then + AMEND= + checkout "$RELEASE" + run git branch -D "$BRANCH" + checkout -b "$BRANCH" "$PRIOR" +fi +# Even when we do have a prior commit, the headers it reports as +# deleted are not actually deleted as part of that commit; so their +# deletion below shall ensure they're reported in the commit message, +# whether AMENDing or not. We could filter these when not AMENDing, +# but (doing so would be fiddly and) any restored would then be +# described as deleted in the first commit's message, without +# mentioning that they're restored in the second (albeit any change in +# them shall show up in the diff). + +# apiheaders commit +# Echoes one header name per line +apiheaders () { + git ls-tree -r --name-only "$1" \ + | grep "^$SRCDIR"'/[-a-zA-Z0-9_/]*\.h$' \ + | grep -vi '^src/tools/' \ + | grep -v _p/ \ + | grep -v '_\(p\|pch\)\.h$' \ + | grep -v '/qt[a-z0-9][a-z0-9]*-config\.h$' \ + | grep -v '/\.' \ + | grep -vi '/\(private\|doc\|tests\|examples\|build\)/' \ + | grep -v '/ui_[^/]*\.h$' \ + | if git checkout "$1" -- $SYNC >&2 + then + mutter "Using $SYNC's list of API headers" + "$THERE"/sync-filter-api-headers \ + || warn "Failed ($?) to filter header list for $1" + else + mutter "No $SYNC in $1: falling back on filtered git ls-tree" + while read f # Add some further kludgy filtering: + do case "$f" in + # qtbase just has to be different: + src/plugins/platforms/eglfs/api/*) echo "$f" ;; + src/3rdparty/angle/include/*) echo "$f" ;; + # Otherwise, plugins and 3rdparty aren't API: + src/plugins/*) ;; + */3rdparty/*) ;; + esac + done + fi +} + +# Make sure any sub-submodules are in their right states: +git submodule update --checkout + +mutter "Purging obsolete headers" # so renames get detected and handled correctly: +apiheaders HEAD | while read h +# Update former API headers, remove them if removed: +do git checkout -q "$RELEASE" -- "$h" || git rm $QUIET -f -- "$h" +done 2>&1 | grep -wv "error: pathspec '.*' did not match any" +mutter "Checking out $RELEASE's API headers" +apiheaders "$RELEASE" | tr '\n' '\0' | \ + xargs -0r git checkout "$RELEASE" -- || die "Failed new header checkout" + +git diff --quiet || die "All changes should initially be staged." +if git diff --quiet --cached "$PRIOR" +then + mutter "Clearing away unused branch and restoring $RESTORE" + git checkout $QUIET "$RESTORE" + git branch -D "$BRANCH" + retire "No changes to API (not even boring ones)" +fi + +mutter "Reverting the boring bits" +run "$THERE/resetboring.py" --disclaim | xargs -r git checkout $QUIET "$PRIOR" -- + +if git diff --quiet --cached "$PRIOR" +then retire "All the change here looks boring: check with git diff -D" +fi + +# Find a good place to prepare our commit message +if [ -f .git ] +then GITDIR=`cut -d ' ' -f 2 <.git` +else GITDIR=.git +fi + +# It's a WIP to discourage any unwitting actual merge ! +( echo "WIP: API comparison from $PRIOR to $RELEASE" + echo + git status | grep 'deleted:' | tr '\t' ' ' + git diff | wc | python -c 'import sys; row = sys.stdin.readline().split(); print; \ + print (("Excluded %s lines (%s words, %s bytes) of boring changes." % tuple(row)) \ + if any(int(x) != 0 for x in row) else "Found nothing boring to ignore.")' + cat <<EOF + +Note to reviewers: if you know of other changes to API, not covered by +this review, fetch this from Gerrit - see its checkout instructions; +you might want to add -b $BRANCH to those - and you +can add files to the result. This can be relevant for behavior +changes in the .cpp or, for changes to documented behavior, in .qdoc +files. + +Just git checkout $RELEASE each relevant file; you can then git reset +HEAD each and git add -p to select the relevant changes or use git +reset -p HEAD to unstage what git checkout has staged. Try not to +include extraneous (non-API) changes. Once you've added what matters, +git commit --amend and push back to gerrit's refs/for/$RELEASE in the +usual way. + +Between staging changes from $RELEASE and committing, you can filter +some boring changes by running qtqa/scripts/api-review/resetboring.py +in the top-level directory of your module; this selectively de-stages +things commonly deemed boring in review. (You'll need the python +dulwich package installed, for this.) You can git add -p after that, +of course, to restore any changes you think aren't boring. + +Remember that the parent of this commit is $PRIOR, not $RELEASE, despite +the review being sent to the latter in Gerrit. +EOF + [ -z "$CHANGEID$TASK" ] || echo + [ -z "$TASK" ] || echo "Task-number: $TASK" + [ -z "$CHANGEID" ] || echo "Change-Id: $CHANGEID" + ) > "$GITDIR/COMMIT_EDITMSG" +# The git status in that holds a lock that precludes the git commit; +# so we can't just pipe the message and use -F - to deliver it. +run git commit $QUIET $AMEND -F "$GITDIR/COMMIT_EDITMSG" + +mention "I recommend you review that what git diff -D reports (now) *is* boring." +mention "(If any of it isn't, you can git add -p it and git commit --amend.)" +mention "Then you can: git push gerrit $BRANCH:refs/for/$RELEASE" |