Skip to content

Instantly share code, notes, and snippets.

@dansimau
Forked from jamesarosen/git-merge-button.md
Last active March 11, 2016 13:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dansimau/e69c8e610c822c97ba04 to your computer and use it in GitHub Desktop.
Save dansimau/e69c8e610c822c97ba04 to your computer and use it in GitHub Desktop.
GitHub's Merge Button™ from the command line

git-gh-merge

git-gh-merge allows you to merge a GitHub pull request from the command line.

If you have multiple email addresses on GitHub, merging locally has the advantage that the merge commit will be from your local/correct email, instead of the primary email of your GitHub account.

Usage

cd path/to/your/repo
git gh-merge 71			# 71 is the pull request number

The script performs the same actions as GitHub's "Merge" button, including deleting the local and remote branches when successfully merged.

Example

Here is the example output from a merge:

$ git gh-merge 75
Merging pull request 75 of uber/ringpopd-go into master: "Fix swim file descritor leak"
Continue? [y/N] y
+ git checkout fix/swim-test-fd-leak
Already on 'fix/swim-test-fd-leak'
Your branch is up-to-date with 'origin/fix/swim-test-fd-leak'.
+ git branch -u origin/fix/swim-test-fd-leak fix/swim-test-fd-leak
Branch fix/swim-test-fd-leak set up to track remote branch fix/swim-test-fd-leak from origin.
+ git pull
Already up-to-date.
+ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
+ git pull
Current branch master is up to date.
+ git merge --no-ff fix/swim-test-fd-leak -m 'Merge branch '\''fix/swim-test-fd-leak'\''

Closes #75'
Merge made by the 'recursive' strategy.
 swim/stats_test.go |  8 --------
 swim/test_utils.go | 13 ++++++++++---
 2 files changed, 10 insertions(+), 11 deletions(-)
+ git push
Counting objects: 1, done.
Writing objects: 100% (1/1), 245 bytes | 0 bytes/s, done.
Total 1 (delta 0), reused 0 (delta 0)
To ssh://git@github.com/uber/ringpop-go
   beb0ed6..ad683a8  master -> master
+ git branch -d fix/swim-test-fd-leak
Deleted branch fix/swim-test-fd-leak (was 22dcbab).
+ git push origin :fix/swim-test-fd-leak
To ssh://git@github.com/uber/ringpop-go
 - [deleted]         fix/swim-test-fd-leak
$

The commands being executed are printed as you go and execution halts if any command fails so you can fix the problem and try again.

Installation

To install, download git-gh-merge somewhere into your $PATH. E.g. to install to /usr/local/bin:

wget -O /usr/local/bin/git-gh-merge \
	https://gist.githubusercontent.com/dansimau/e69c8e610c822c97ba04/raw/git-gh-merge && \
chmod +x /usr/local/bin/git-gh-merge

Credits

Based off this gist by jamesarosen.

LICENSE

MIT

#!/bin/bash
#
# Local equivalent of GitHub's "merge" button.
#
# 2016-03-10
# dan@dans.im
#
declare ORIGIN="origin"
declare NOOP=false
# Exit immediately on error
set -e
# Check for correct usage
if [[ $# -lt 1 ]] || [[ "$1" =~ --?h ]]; then
echo "Usage: $0 <PR number> [-v]" >&2
echo "-v means print commands that would be executed, but don't execute them" >&2
exit 99
fi
if ! type curl &>/dev/null; then
echo "ERROR: Missing 'curl'" >&2
exit 1
fi
if ! type jq &>/dev/null; then
echo "ERROR: Missing 'jq'" >&2
exit 1
fi
# Check we're in a repo
if ! git status --porcelain &>/dev/null; then
echo "ERROR: $PWD is not a repository" >&2
exit 1
fi
if [ "$2" == "-v" ]; then
NOOP=true
fi
#
# Execute the specified commands, unless NOOP=true, then just print what would
# be executed.
#
_do() {
if $NOOP; then
echo "$@"
else
(set -x; "$@")
fi
}
#
# Print the URL for the specified git remote.
#
# $1: Name of the remote
#
_git_remote_url() {
git remote -v |grep "$1" |head -n1 |awk '{print $2}'
}
#
# Print the GitHub repo (<user>/<repo>) from the specified URL.
#
# $1: GitHub URL
#
_github_repo_from_url() {
local url=$1
local _tmp
# Extract regex from the back of $url
_tmp=$(expr "$url" : '.*[/:]\(.*/.*\)')
# Remove trailing .git
_tmp=${_tmp%%.git}
if [ "$_tmp" == "" ]; then
echo "ERROR: Unable to extract GitHub repo name from URL." >&2
echo >&2
echo "URL: $url" >&2
return 1
else
echo $_tmp
fi
}
#
# Fetch data about the specified PR from Github.
#
# $1: Github repo (<user>/<repo>)
# $1: PR number
#
_fetch_pr_data_from_github() {
local repo=$1
local pr=$2
/usr/bin/curl -s https://api.github.com/repos/${repo}/pulls/${pr}
}
#
# Extract the branch name in the specified Github PR API data.
#
# $1: JSON data from Github
#
_github_pr_branch() {
local api_data=$1
local branch_name=$(echo "$api_data" |jq -r '.head.ref')
if [ "$branch_name" == "null" ]; then
echo "ERROR: Unable to extract branch name." >&2
echo >&2
echo "API data: $api_data" >&2
return 1
else
echo $branch_name
fi
}
#
# Extract the merge base from the Github PR API data.
#
_github_pr_merge_base() {
local api_data=$1
local merge_base=$(echo "$api_data" |jq -r '.base.ref')
if [ "$merge_base" == "null" ]; then
echo "ERROR: Unable to extract merge base." >&2
echo >&2
echo "API data: $api_data" >&2
return 1
else
echo $merge_base
fi
}
#
# Extract the PR name from the Github PR API data.
#
_github_pr_name() {
local api_data=$1
local name=$(echo "$api_data" |jq -r '.title')
if [ "$name" == "null" ]; then
echo "ERROR: Unable to extract PR name." >&2
echo >&2
echo "API data: $api_data" >&2
return 1
else
echo $name
fi
}
GH_PR_ID=$1
GH_REPO_URL=$(_git_remote_url $ORIGIN)
GH_REPO_NAME=$(_github_repo_from_url $GH_REPO_URL)
GH_PR_API_DATA="$(_fetch_pr_data_from_github $GH_REPO_NAME $GH_PR_ID)"
GH_PR_BRANCH_NAME=$(_github_pr_branch "$GH_PR_API_DATA")
GH_PR_MERGE_BASE=$(_github_pr_merge_base "$GH_PR_API_DATA")
GH_PR_NAME=$(_github_pr_name "$GH_PR_API_DATA")
# Display confirmation
if ! $NOOP; then
echo -e "Merging pull request \033[96m\033[1m${GH_PR_ID}\033[0m of \033[96m\033[1m${GH_REPO_NAME}\033[0m into \033[45m\033[1m${GH_PR_MERGE_BASE}\033[0m: \"$GH_PR_NAME\""
echo -n "Continue? [y/N] "
read response
if [ "$response" != "y" ]; then
echo "Aborting"
exit 1
fi
fi
if $NOOP; then
echo "# These commands would be executed without -v:"
fi
_do git checkout $GH_PR_BRANCH_NAME
_do git branch -u $ORIGIN/$GH_PR_BRANCH_NAME $GH_PR_BRANCH_NAME
_do git pull
_do git checkout $GH_PR_MERGE_BASE
_do git pull
_do git merge --no-ff $GH_PR_BRANCH_NAME -m "Merge branch '$GH_PR_BRANCH_NAME'"$'\n'$'\n'"Closes #$GH_PR_ID"
_do git push
_do git branch -d $GH_PR_BRANCH_NAME
_do git push origin ":$GH_PR_BRANCH_NAME"
Copyright (c) 2016 Daniel Simmons
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment