Page MenuHomeFreeBSD

D28334.id82858.diff
No OneTemporary

D28334.id82858.diff

Index: tools/tools/git/Makefile
===================================================================
--- /dev/null
+++ tools/tools/git/Makefile
@@ -0,0 +1,4 @@
+SCRIPTS= git-arc.sh
+BINDIR?= /usr/local/bin
+
+.include <bsd.prog.mk>
Index: tools/tools/git/git-arc.sh
===================================================================
--- /dev/null
+++ tools/tools/git/git-arc.sh
@@ -0,0 +1,588 @@
+#!/bin/sh
+#
+# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+#
+# Copyright (c) 2019-2021 Mark Johnston <markj@FreeBSD.org>
+# Copyright (c) 2021 John Baldwin <jhb@FreeBSD.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+
+# TODO:
+# - roll back after errors or SIGINT
+# - current checkout
+# - created revs
+# - main (for git arc stage)
+
+warn()
+{
+ echo "$(basename $0): $1" >&2
+}
+
+err()
+{
+ warn "$1"
+ exit 1
+}
+
+err_usage()
+{
+ cat >&2 <<__EOF__
+Usage: git arc [-vy] <verb> <arguments>
+
+Verbs:
+ create [-l] [-r <reviewer1>[,<reviewer2>...]] [-s subscriber[,...]] [<commit>|<commit range>]
+ list <commit>|<commit range>
+ patch <diff1> [<diff2> ...]
+ stage [-b branch] [<commit>|<commit range>]
+ update <commit>
+
+Description:
+ Create or manage FreeBSD Phabricator reviews based on git commits. There
+ is a one-to one relationship between git commits and Differential revisions,
+ and the Differential revision title must match the summary line of the
+ corresponding commit. In particular, commit summaries must be unique across
+ all open Differential revisions authored by you.
+
+ The first parameter must be a verb. The available verbs are:
+
+ create -- Create new Differential revisions from the specified commits.
+ list -- Print the associated Differential revisions for the specified
+ commits.
+ patch -- Try to apply a patch from a Differential revision to the
+ currently checked out tree.
+ stage -- Prepare a series of commits to be pushed to the upstream FreeBSD
+ repository. The commits are cherry-picked to a branch (main by
+ default), review tags are added to the commit log message, and
+ the log message is opened in an editor for any last-minute
+ updates. The commits need not have associated Differential
+ revisions.
+ update -- Synchronize the Differential revisions associated with the
+ specified commits. Currently only the diff is updated; the
+ review description and other metadata is not synchronized.
+
+ The typical end-to-end usage looks something like this:
+
+ $ git commit -m "kern: Rewrite in Rust"
+ $ git arc create HEAD
+ <Make changes to the diff based on reviewer feedback.>
+ $ git commit --amend
+ $ git arc update HEAD
+ <Now that all reviewers are happy, it's time to push.>
+ $ git arc stage HEAD
+ $ git push upstream HEAD:main
+
+Config Variables:
+ These are manipulated by git-config(1).
+
+ arc.assume_yes [bool]
+ -- Assume a "yes" answer to all prompts instead of
+ prompting the user. Equivalent to the -y flag.
+
+ arc.browse [bool] -- Try to open newly created reviews in a browser tab.
+ Defaults to false.
+
+ arc.list [bool] -- Always use "list mode" (-l) with create. In this
+ mode, the list of git revisions to create reviews for
+ is listed with a single prompt before creating
+ reviews. The diffs for individual commits are not
+ shown.
+
+ arc.verbose [bool] -- Verbose output. Equivalent to the -v flag.
+
+Examples:
+ Create a Phabricator review using the contents of the most recent commit in
+ your git checkout. The commit title is used as the review title, the commit
+ log message is used as the review description, markj@FreeBSD.org is added as
+ a reviewer.
+
+ $ git arc create -r markj HEAD
+
+ Create a series of Phabricator reviews for each of HEAD~2, HEAD~ and HEAD.
+ Pairs of consecutive commits are linked into a patch stack. Note that the
+ first commit in the specified range is excluded.
+
+ $ git arc create HEAD~3..HEAD
+
+ Update the review corresponding to commit b409afcfedcdda. The title of the
+ commit must be the same as it was when the review was created. The review
+ description is not automatically updated.
+
+ $ git arc update b409afcfedcdda
+
+ Apply the patch in review D12345 to the currently checked-out tree, and stage
+ it.
+
+ $ git arc patch D12345
+
+ List the status of reviews for all the commits in the branch "feature":
+
+ $ git arc list main..feature
+
+__EOF__
+
+ exit 1
+}
+
+diff2phid()
+{
+ local diff
+
+ diff=$1
+ if ! expr "$diff" : 'D[1-9][0-9]*$' >/dev/null; then
+ err "invalid diff ID $diff"
+ fi
+
+ echo '{"names":["'$diff'"]}' |
+ arc call-conduit -- phid.lookup |
+ jq -r "select(.response != []) | .response.${diff}.phid"
+}
+
+diff2status()
+{
+ local diff tmp status summary
+
+ diff=$1
+ if ! expr "$diff" : 'D[1-9][0-9]*$' >/dev/null; then
+ err "invalid diff ID $diff"
+ fi
+
+ tmp=$(mktemp)
+ echo '{"names":["'$diff'"]}' |
+ arc call-conduit -- phid.lookup > $tmp
+ status=$(jq -r "select(.response != []) | .response.${diff}.status" < $tmp)
+ summary=$(jq -r "select(.response != []) |
+ .response.${diff}.fullName" < $tmp)
+ printf "%-14s %s\n" "${status}" "${summary}"
+}
+
+log2diff()
+{
+ local diff
+
+ diff=$(git show -s --format=%B $commit |
+ sed -nE '/^Differential Revision:[[:space:]]+(https:\/\/reviews.freebsd.org\/)?(D[0-9]+)$/{s//\2/;p;}')
+ if [ -n "$diff" ] && [ $(echo "$diff" | wc -l) -eq 1 ]; then
+ echo $diff
+ else
+ echo
+ fi
+}
+
+commit2diff()
+{
+ local commit diff title
+
+ commit=$1
+
+ # First, look for a valid differential reference in the commit
+ # log.
+ diff=$(log2diff $commit)
+ if [ -n "$diff" ]; then
+ echo $diff
+ return
+ fi
+
+ # Second, search the open reviews returned by 'arc list' looking
+ # for a subject match.
+ title=$(git show -s --format=%s $commit)
+ diff=$(arc list | fgrep "$title" | egrep -o 'D[1-9][0-9]*:' | tr -d ':')
+ if [ -z "$diff" ]; then
+ err "could not find review for '${title}'"
+ elif [ $(echo "$diff" | wc -l) -ne 1 ]; then
+ err "found multiple reviews with the same title"
+ fi
+
+ echo $diff
+}
+
+create_one_review()
+{
+ local childphid commit dir doprompt msg parent parentphid reviewers
+ local subscribers
+
+ commit=$1
+ reviewers=$2
+ subscribers=$3
+ parent=$4
+ doprompt=$5
+
+ if [ "$doprompt" ] && ! show_and_prompt $commit; then
+ return 1
+ fi
+
+ git checkout -q $commit
+
+ dir=$(git rev-parse --git-dir)/arc
+ mkdir -p "$dir"
+
+ msg=${dir}/create-message
+ git show -s --format='%B' $commit > $msg
+ printf "\nTest Plan:\n" >> $msg
+ printf "\nReviewers:\n" >> $msg
+ printf "${reviewers}\n" >> $msg
+ printf "\nSubscribers:\n" >> $msg
+ printf "${subscribers}\n" >> $msg
+
+ yes | env EDITOR=true \
+ arc diff --never-apply-patches --create --allow-untracked $BROWSE HEAD~
+ [ $? -eq 0 ] || err "could not create Phabricator diff"
+
+ if [ -n "$parent" ]; then
+ diff=$(commit2diff $commit)
+ [ -n "$diff" ] || err "failed to look up review ID for $commit"
+
+ childphid=$(diff2phid $diff)
+ parentphid=$(diff2phid $parent)
+ echo '{
+ "objectIdentifier": "'${childphid}'",
+ "transactions": [
+ {
+ "type": "parents.add",
+ "value": ["'${parentphid}'"]
+ }
+ ]}' |
+ arc call-conduit -- differential.revision.edit >&3
+ fi
+ return 0
+}
+
+# Get a list of reviewers who accepted the specified diff.
+diff2reviewers()
+{
+ local diff phid reviewid userids
+
+ diff=$1
+ reviewid=$(diff2phid $diff)
+ userids=$( \
+ echo '{
+ "constraints": {"phids": ["'$reviewid'"]},
+ "attachments": {"reviewers": true}
+ }' |
+ arc call-conduit -- differential.revision.search |
+ jq '.response.data[0].attachments.reviewers.reviewers[] | select(.status == "accepted").reviewerPHID')
+ if [ -n "$userids" ]; then
+ echo '{
+ "constraints": {"phids": ['$(echo -n $userids | tr '[:space:]' ',')']}
+ }' |
+ arc call-conduit -- user.search |
+ jq -r '.response.data[].fields.username'
+ fi
+}
+
+prompt()
+{
+ local resp
+
+ if [ "$ASSUME_YES" ]; then
+ return 1
+ fi
+
+ printf "\nDoes this look OK? [y/N] "
+ read resp
+
+ case $resp in
+ [Yy])
+ return 0
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+}
+
+show_and_prompt()
+{
+ local commit
+
+ commit=$1
+
+ git show $commit
+ prompt
+}
+
+save_head()
+{
+ local commit orig
+
+ if ! orig=$(git symbolic-ref --short -q HEAD); then
+ orig=$(git show -s --pretty=%H HEAD)
+ fi
+ echo $orig
+}
+
+restore_head()
+{
+ local orig
+
+ orig=$1
+ git checkout -q $orig
+}
+
+build_commit_list()
+{
+ local chash _commits commits
+
+ for chash in $@; do
+ _commits=$(git rev-parse "${chash}")
+ if ! git cat-file -e "${chash}"'^{commit}' >/dev/null 2>&1; then
+ _commits=$(git rev-list $_commits | tail -r)
+ fi
+ [ -n "$_commits" ] || err "invalid commit ID ${chash}"
+ commits="$commits $_commits"
+ done
+ echo $commits
+}
+
+gitarc::create()
+{
+ local commit commits doprompt list o orig prev reviewers
+ local subscribers
+
+ list=
+ if eval $(git config --bool --default false --get arc.list); then
+ list=1
+ fi
+ doprompt=1
+ while getopts lr:s: o; do
+ case "$o" in
+ l)
+ list=1
+ ;;
+ r)
+ reviewers="$OPTARG"
+ ;;
+ s)
+ subscribers="$OPTARG"
+ ;;
+ *)
+ err_usage
+ ;;
+ esac
+ done
+ shift $((OPTIND-1))
+
+ commits=$(build_commit_list $@)
+
+ if [ "$list" ]; then
+ for commit in ${commits}; do
+ git --no-pager show --oneline --no-patch $commit
+ done | git_pager
+ if ! prompt; then
+ return
+ fi
+ doprompt=
+ fi
+
+ orig=$(save_head)
+ prev=""
+ for commit in ${commits}; do
+ if create_one_review "$commit" "$reviewers" "$subscribers" "$prev" \
+ "$doprompt"; then
+ prev=$(commit2diff "$commit")
+ else
+ prev=""
+ fi
+ done
+ restore_head $orig
+}
+
+gitarc::list()
+{
+ local chash commit commits diff title
+
+ commits=$(build_commit_list $@)
+
+ for commit in $commits; do
+ chash=$(git show -s --format='%C(auto)%h' $commit)
+ echo -n "${chash} "
+
+ diff=$(log2diff $commit)
+ if [ -n "$diff" ]; then
+ diff2status $diff
+ continue
+ fi
+
+ # This does not use commit2diff as it needs to handle errors
+ # differently and keep the entire status. The extra 'cat'
+ # after 'fgrep' avoids erroring due to -e.
+ title=$(git show -s --format=%s $commit)
+ diff=$(arc list | fgrep "$title" | cat)
+ if [ -z "$diff" ]; then
+ echo "No Review : $title"
+ elif [ $(echo "$diff" | wc -l) -ne 1 ]; then
+ echo -n "Ambiguous Reviews: "
+ echo "$diff" | egrep -o 'D[1-9][0-9]*:' | tr -d ':' \
+ | paste -sd ',' - | sed 's/,/, /g'
+ else
+ echo "$diff" | sed -e 's/^[^ ]* *//'
+ fi
+ done
+}
+
+gitarc::patch()
+{
+ local rev
+
+ if [ $# -eq 0 ]; then
+ err_usage
+ fi
+
+ for rev in $@; do
+ arc patch --skip-dependencies --nocommit --nobranch --force $rev
+ echo "Applying ${rev}..."
+ [ $? -eq 0 ] || break
+ done
+}
+
+gitarc::stage()
+{
+ local branch commit commits diff reviewers tmp
+
+ branch=main
+ while getopts b: o; do
+ case "$o" in
+ b)
+ branch="$OPTARG"
+ ;;
+ *)
+ err_usage
+ ;;
+ esac
+ done
+ shift $((OPTIND-1))
+
+ commits=$(build_commit_list $@)
+
+ if [ "$branch" = "main" ]; then
+ git checkout -q main
+ else
+ git checkout -q -b ${branch} main
+ fi
+
+ tmp=$(mktemp)
+ for commit in $commits; do
+ git show -s --format=%B $commit > $tmp
+ diff=$(arc list | fgrep "$(git show -s --format=%s $commit)" |
+ egrep -o 'D[1-9][0-9]*:' | tr -d ':')
+ if [ -n "$diff" ]; then
+ # XXX this leaves an extra newline in some cases.
+ reviewers=$(diff2reviewers $diff | sed '/^$/d' | paste -sd ',' - | sed 's/,/, /g')
+ if [ -n "$reviewers" ]; then
+ printf "Reviewed by:\t${reviewers}\n" >> $tmp
+ fi
+ printf "Differential Revision:\thttps://reviews.freebsd.org/${diff}" >> $tmp
+ fi
+ if ! git cherry-pick --no-commit ${commit}; then
+ warn "Failed to apply $(git rev-parse --short ${commit}). Are you staging patches in the wrong order?"
+ git checkout -f
+ break
+ fi
+ git commit --edit --file $tmp
+ done
+}
+
+gitarc::update()
+{
+ local commit diff orig
+
+ commit=$1
+ diff=$(commit2diff $commit)
+
+ if ! show_and_prompt $commit; then
+ return
+ fi
+
+ orig=$(save_head)
+ git checkout -q $commit
+
+ # The linter is stupid and applies patches to the working copy.
+ # This would be tolerable if it didn't try to correct "misspelled" variable
+ # names.
+ arc diff --allow-untracked --never-apply-patches --update $diff HEAD~
+
+ restore_head $orig
+}
+
+set -e
+
+ASSUME_YES=
+if eval $(git config --bool --default false --get arc.assume-yes); then
+ ASSUME_YES=1
+fi
+
+VERBOSE=
+while getopts vy o; do
+ case "$o" in
+ v)
+ VERBOSE=1
+ ;;
+ y)
+ ASSUME_YES=1
+ ;;
+ *)
+ err_usage
+ ;;
+ esac
+done
+shift $((OPTIND-1))
+
+[ $# -ge 1 ] || err_usage
+
+if [ "$VERBOSE" ]; then
+ exec 3>&1
+else
+ exec 3> /dev/null
+fi
+
+case "$1" in
+create|list|patch|stage|update)
+ ;;
+*)
+ err_usage
+ ;;
+esac
+verb=$1
+shift
+
+# Pull in some git helper functions.
+git_sh_setup=$(git --exec-path)/git-sh-setup
+[ -f "$git_sh_setup" ] || err "cannot find git-sh-setup"
+SUBDIRECTORY_OK=y
+USAGE=
+. "$git_sh_setup"
+
+# Bail if the working tree is unclean, except for "list" and "patch"
+# operations.
+case $verb in
+list|patch)
+ ;;
+*)
+ require_clean_work_tree $verb
+ ;;
+esac
+
+if eval $(git config --bool --default false --get arc.browse); then
+ BROWSE=--browse
+fi
+
+gitarc::${verb} $@

File Metadata

Mime Type
text/plain
Expires
Sun, Jan 19, 10:47 AM (16 h, 6 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
15939274
Default Alt Text
D28334.id82858.diff (16 KB)

Event Timeline