From ea105ffec52f9f174a442d882b6f35790ea15594 Mon Sep 17 00:00:00 2001 From: derAnfaenger Date: Fri, 8 Sep 2017 10:41:25 +0200 Subject: [PATCH] tools: Add git utility scripts. --- tools/clean-branches | 70 +++++++++++++++++++++++++++++++++ tools/clean-repo | 13 ++++++ tools/deploy-branch | 43 ++++++++++++++++++++ tools/fetch-pull-request | 16 ++++++++ tools/fetch-rebase-pull-request | 17 ++++++++ tools/reset-to-pull-request | 15 +++++++ tools/review | 68 ++++++++++++++++++++++++++++++++ 7 files changed, 242 insertions(+) create mode 100644 tools/clean-branches create mode 100644 tools/clean-repo create mode 100644 tools/deploy-branch create mode 100644 tools/fetch-pull-request create mode 100644 tools/fetch-rebase-pull-request create mode 100644 tools/reset-to-pull-request create mode 100644 tools/review diff --git a/tools/clean-branches b/tools/clean-branches new file mode 100644 index 0000000..915491b --- /dev/null +++ b/tools/clean-branches @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +set -e + +# usage: clean-branches +# Deletes any local branches which are ancestors of origin/master, +# and also any branches in origin which are ancestors of +# origin/master and are named like $USER-*. + +# usage: clean-branches --reviews +# Deletes all the above mentioned branches as well as branches +# created by the scripts like `fetch-rebase-pull-request`. Be careful +# as this would also remove other branches woth names like review-* + +review=0 +if [ $# -ne 0 ] && [ "$1" == "--reviews" ]; then + review=1 +fi +push_args=() + +function is_merged { + ! git rev-list -n 1 origin/master.."$1" | grep -q . +} + +function clean_ref { + ref="$1" + case "$ref" in + */master | */HEAD) + return + ;; + + refs/heads/review-*) + if [ $review -ne 0 ]; then + echo -n "Deleting local branch $(echo "$ref" | sed 's!^refs/heads/!!')" + echo " (was $(git rev-parse --short "$ref"))" + git update-ref -d "$ref" + fi + ;; + + refs/heads/*) + if is_merged "$ref"; then + echo -n "Deleting local branch $(echo "$ref" | sed 's!^refs/heads/!!')" + echo " (was $(git rev-parse --short "$ref"))" + git update-ref -d "$ref" + fi + ;; + + refs/remotes/origin/$USER-*) + if is_merged "$ref"; then + remote_name="$(echo "$ref" | sed 's!^refs/remotes/origin/!!')" + echo -n "Deleting remote branch $remote_name" + echo " (was $(git rev-parse --short "$ref"))" + # NB: this won't handle spaces in ref names + push_args=("${push_args[@]}" ":$remote_name") + fi + ;; + esac +} + +if [ "$(git symbolic-ref HEAD)" != 'refs/heads/master' ]; then + echo "Check out master before you run this script." >&2 + exit 1 +fi + +git fetch --prune origin + +eval "$(git for-each-ref --shell --format='clean_ref %(refname);')" + +if [ "${#push_args}" -ne 0 ]; then + git push origin "${push_args[@]}" +fi diff --git a/tools/clean-repo b/tools/clean-repo new file mode 100644 index 0000000..f1615a4 --- /dev/null +++ b/tools/clean-repo @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -e + +# Remove .pyc files to prevent loading stale code. +# +# You can run it automatically on checkout: +# +# echo ./tools/clean-repo > .git/hooks/post-checkout +# chmod +x .git/hooks/post-checkout + +cd "$(dirname "$0")/.." +find . -name "*.pyc" -exec rm -f {} \; +find . -name "__pycache__" -prune -exec rm -rf {} \; diff --git a/tools/deploy-branch b/tools/deploy-branch new file mode 100644 index 0000000..4755f2c --- /dev/null +++ b/tools/deploy-branch @@ -0,0 +1,43 @@ +#!/bin/bash + +function error_out { + echo -en '\e[0;31m' + echo "$1" + echo -en '\e[0m' + exit 1 +} + +status=$(git status --porcelain | grep -v '^??') +[ ! -z "$status" ] && error_out "Working directory or index not clean" + +old_ref=$(git rev-list --max-count=1 HEAD) +branch=$1 +branch_ref=$(git rev-list --max-count=1 "$branch") + +[ $? -ne 0 ] && error_out "Unknown branch: $branch" + +if [ "$old_ref" == "$branch_ref" ]; then + new_ref=master +else + ref_name=$(git describe --all --exact "$old_ref") + if [ $? -eq 0 ]; then + new_ref=$(echo "$ref_name" | perl -pe 's{^(heads|remotes)/}{}') + else + new_ref=$old_ref + fi +fi + +[ -z "$branch" ] && error_out "You must specify a branch name to deploy" + +git fetch -p + +git rebase origin/master "$branch" +[ $? -ne 0 ] && error_out "Rebase onto origin/master failed" + +git push . HEAD:master +git push origin master +[ $? -ne 0 ] && error_out "Push of master to origin/master failed" + +git checkout "$new_ref" +git branch -D "$branch" +git push origin ":$branch" diff --git a/tools/fetch-pull-request b/tools/fetch-pull-request new file mode 100644 index 0000000..c8e3531 --- /dev/null +++ b/tools/fetch-pull-request @@ -0,0 +1,16 @@ +#!/bin/bash +set -e +set -x + +if ! git diff-index --quiet HEAD; then + set +x + echo "There are uncommitted changes:" + git status --short + echo "Doing nothing to avoid losing your work." + exit 1 +fi +request_id="$1" +remote=${2:-"upstream"} +git fetch "$remote" "pull/$request_id/head" +git checkout -B "review-original-${request_id}" +git reset --hard FETCH_HEAD diff --git a/tools/fetch-rebase-pull-request b/tools/fetch-rebase-pull-request new file mode 100644 index 0000000..ab3fec3 --- /dev/null +++ b/tools/fetch-rebase-pull-request @@ -0,0 +1,17 @@ +#!/bin/bash +set -e +set -x + +if ! git diff-index --quiet HEAD; then + set +x + echo "There are uncommitted changes:" + git status --short + echo "Doing nothing to avoid losing your work." + exit 1 +fi +request_id="$1" +remote=${2:-"upstream"} +git fetch "$remote" "pull/$request_id/head" +git checkout -B "review-${request_id}" $remote/master +git reset --hard FETCH_HEAD +git pull --rebase diff --git a/tools/reset-to-pull-request b/tools/reset-to-pull-request new file mode 100644 index 0000000..405a992 --- /dev/null +++ b/tools/reset-to-pull-request @@ -0,0 +1,15 @@ +#!/bin/bash +set -e +set -x + +if ! git diff-index --quiet HEAD; then + set +x + echo "There are uncommitted changes:" + git status --short + echo "Doing nothing to avoid losing your work." + exit 1 +fi +request_id="$1" +remote=${2:-"upstream"} +git fetch "$remote" "pull/$request_id/head" +git reset --hard FETCH_HEAD diff --git a/tools/review b/tools/review new file mode 100644 index 0000000..43a95ec --- /dev/null +++ b/tools/review @@ -0,0 +1,68 @@ +#!/usr/bin/env python + +from __future__ import print_function +import subprocess +import sys + +def exit(message): + # type: (str) -> None + print('PROBLEM!') + print(message) + sys.exit(1) + +def run(command): + # type: (str) -> None + print('\n>>> ' + command) + subprocess.check_call(command.split()) + +def check_output(command): + # type: (str) -> str + return subprocess.check_output(command.split()).decode('ascii') + +def get_git_branch(): + # type: () -> str + command = 'git rev-parse --abbrev-ref HEAD' + output = check_output(command) + return output.strip() + +def check_git_pristine(): + # type: () -> None + command = 'git status --porcelain' + output = check_output(command) + if output.strip(): + exit('Git is not pristine:\n' + output) + +def ensure_on_clean_master(): + # type: () -> None + branch = get_git_branch() + if branch != 'master': + exit('You are still on a feature branch: %s' % (branch,)) + check_git_pristine() + run('git fetch upstream master') + run('git rebase upstream/master') + +def create_pull_branch(pull_id): + # type: (int) -> None + run('git fetch upstream pull/%d/head' % (pull_id,)) + run('git checkout -B review-%s FETCH_HEAD' % (pull_id,)) + run('git rebase upstream/master') + run('git log upstream/master.. --oneline') + run('git diff upstream/master.. --name-status') + + print() + print('PR: %d' % (pull_id,)) + print(subprocess.check_output(['git', 'log', 'HEAD~..', + '--pretty=format:Author: %an'])) + +def review_pr(): + # type: () -> None + try: + pull_id = int(sys.argv[1]) + except Exception: + exit('please provide an integer pull request id') + + ensure_on_clean_master() + create_pull_branch(pull_id) + +if __name__ == '__main__': + review_pr()