Skip to content

Instantly share code, notes, and snippets.

@mildred
Last active February 5, 2018 13:43
Show Gist options
  • Save mildred/b85295738b054c504080fed3eb9c9188 to your computer and use it in GitHub Desktop.
Save mildred/b85295738b054c504080fed3eb9c9188 to your computer and use it in GitHub Desktop.
Pull requests from Git CLI

Put this in your PATH. Requires git-hub

#!/bin/bash
SUBDIRECTORY_OK=1
USAGE="REMOTE [[BASE] [+]REF[:BRANCH]] - Open a Pull request for BASE..REF"
LONG_USAGE="ARGUMENTS
BASE
defaults to REF^
+
Implies -f
REF
defaults to HEAD
BRANCH
name of the branch on the remote (replaces -n BRANCH)
OPTIONS
-n NAME
Name of the branch on the remote
-f Force push
--dry-run
"
. "$(git --exec-path)/git-sh-setup"
op_name=
op_base=
op_ref=
op_remote=origin
op_force=
op_force_plus=
op_dry_run=false
while true; do
case "$1" in
-h|--help)
usage
exit 0
;;
-n)
shift
op_name="$1"
;;
-f)
op_force=-f
;;
--dry-run)
op_dry_run=true
;;
*)
break
;;
esac
shift
done
case $# in
1)
op_remote="$1"
op_ref=HEAD
;;
2)
op_remote="$1"
op_ref="$2"
;;
3)
op_remote="$1"
op_base="$2"
op_ref="$3"
;;
*)
shift 3
echo "Error: extra command-line arguments: $*" >&2
exit 1
esac
if [[ -z "$op_base" ]] && [[ "${op_remote%%/*}" != "$op_remote" ]]; then
op_base="$op_remote"
op_remote="${op_remote%%/*}"
fi
if [[ "$op_remote" == '-' ]]; then
op_remote=origin
elif [[ "$op_remote" != origin ]]; then
die "Error: remote $op_remote not supported. Pleaase use 'origin'"
fi
if [[ -z "$op_name" ]] && [[ "${op_ref//:/}" != "$op_ref" ]]; then
op_name="${op_ref##*:}"
fi
if [[ "${op_ref#+}" != "$op_ref" ]]; then
op_force_plus=+
fi
op_ref="${op_ref%%:*}"
op_ref="${op_ref#+}"
note-pr(){
git notes show "$2" 2>/dev/null | egrep "^Pull-Request: $1 " | cut -d' ' -f3-
}
note-branch(){
git notes show "$2" 2>/dev/null | egrep "^Pull-Request-Branch: $1 " | cut -d' ' -f3-
}
if [[ -z "$op_name" ]]; then
op_name="$(note-branch "$op_remote" "$op_ref")"
fi
if [[ -z "$op_name" ]]; then
op_name="$(git log -1 --format=%D "$op_ref" | tr -d , | xargs -n 1 | sed -n "s:^$op_remote/::p" | grep -v '^HEAD$')"
if [[ -n "$op_name" ]] && [[ $(wc -w <<<"$op_name") -gt 1 ]]; then
echo "Commit $op_name corresponds to multiple remote branches:"
echo "$op_name" | xargs -n 1 printf "\t%s/%s\n" "$op_remote"
op_name="$(head -n 1 <<<"$op_name")"
fi
fi
if [[ -z "$op_name" ]]; then
if name="$(git symbolic-ref --short "$op_ref")"; then
op_name="$name"
if [[ $op_name == master ]]; then
op_name=
fi
fi
fi
if [[ -z "$op_name" ]]; then
op_name="$op_ref"
fi
if [[ -z "$op_name" ]]; then
die "Error: you need to specify a remote branch name using -n flag"
fi
pr_url="$(note-pr "$op_remote" "$op_ref")"
pr_branch="$(note-branch "$op_remote" "$op_ref")"
if [[ -z "$op_base" ]]; then
n=1
op_base="$op_ref^"
if [[ -n "$pr_url" ]]; then
while [[ "$pr_url" = "$(note-pr "$op_remote" "$op_base")" ]] && [[ "$pr_branch" == "$(note-branch "$op_remote" "$op_base")" ]]; do
n=$(($n+1))
op_base="$op_ref~$n"
done
fi
echo "Using base commit $op_base"
fi
log(){
if [ "a$1" = a-always ]; then
shift
elif $op_dry_run; then
echo "- $*" >&2
return 0
fi
(set -x; "$@")
}
if [[ "${op_base#$op_remote/}" != "$op_base" ]]; then
base_branch="${op_base#$op_remote/}"
else
base_branch="$(note-branch "$op_remote" "$op_base")"
if [[ -z "$base_branch" ]]; then
base_branch="$(git log -1 --format=%D "$op_base" | tr -d , | xargs -n 1 | sed -n "s:^$op_remote/::p" | grep -v '^HEAD$')"
if [[ -z "$base_branch" ]]; then
die "Error: base commit $op_base corresponds to no remote branch."
elif [[ $(wc -w <<<"$base_branch") -gt 1 ]]; then
echo "Base commit $op_base corresponds to multiple remote branches:"
echo "$base_branch" | xargs -n 1 printf "\t%s/%s\n" "$op_remote"
base_branch="$(head -n 1 <<<"$base_branch")"
fi
fi
if [[ -z "$base_branch" ]]; then
die "Error: base commit $op_base does not corresponds to a branch in $op_remote. Submit a pull request for that commit first."
fi
fi
echo "Using base branch for pull request $op_remote/$base_branch"
if [[ "$(git rev-parse "refs/remotes/$op_remote/$op_name" -- 2>/dev/null)" != "$(git rev-parse "$op_ref")" ]]; then
echo
if ! log git push $op_force "$op_remote" "$op_force_plus$op_ref:refs/heads/$op_name"; then
exit 1
fi
fi
echo
for commit in $(git rev-list "$op_base..$op_ref"); do
( git notes show "$commit" 2>/dev/null | egrep -v "^(Pull-Request-Branch): $op_remote "
echo "Pull-Request-Branch: $op_remote $op_name"
) | ( git notes add -f -F /dev/stdin "$commit" 2>/dev/null )
log git notes show "$commit" | cat
done
for commit in $(git rev-list "$op_base..$op_ref"); do
git log -1 --format=%B "$commit"
echo
echo
done >"$GIT_DIR/PULLREQ_EDITMSG"
UPDIR="$(git rev-parse --show-cdup)"
if [ -f "${UPDIR}.github/PULL_REQUEST_TEMPLATE.md" ]; then
(
cat "${UPDIR}.github/PULL_REQUEST_TEMPLATE.md"
echo
echo
) >>"$GIT_DIR/PULLREQ_EDITMSG"
fi
git log "$op_base..$op_ref" | sed 's/^/## /' >>"$GIT_DIR/PULLREQ_EDITMSG"
git_editor "$GIT_DIR/PULLREQ_EDITMSG"
sed -i '/^## /d' "$GIT_DIR/PULLREQ_EDITMSG"
if ! [[ -s "$GIT_DIR/PULLREQ_EDITMSG" ]]; then
die "Abort: no message specified"
fi
if false && [[ -n "$pr_url" ]]; then
echo
if ! log hub pull-request -i "$pr_url" -F "$GIT_DIR/PULLREQ_EDITMSG" -b "$base_branch" -h "$op_name"; then
exit 1
fi
else
echo
if ! pr_url="$(log hub pull-request -f -F "$GIT_DIR/PULLREQ_EDITMSG" -b "$base_branch" -h "$op_name")"; then
echo "$pr_url"
exit 1
fi
echo "$pr_url"
fi
echo
for commit in $(git rev-list "$op_base..$op_ref"); do
( git notes show "$commit" 2>/dev/null | egrep -v "^(Pull-Request): $op_remote "
echo "Pull-Request: $op_remote $pr_url"
) | ( git notes add -f -F /dev/stdin "$commit" 2>/dev/null )
log git notes show "$commit" | cat
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment