summaryrefslogtreecommitdiffstats
path: root/scripts/api-review/api-review-gen
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/api-review/api-review-gen')
-rwxr-xr-xscripts/api-review/api-review-gen376
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"