Created
April 27, 2020 13:12
-
-
Save felix-d/629d690b44feb1737d381eda6fad3946 to your computer and use it in GitHub Desktop.
Review git branches
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# frozen_string_literal: true | |
require 'optparse' | |
require 'ostruct' | |
require 'net/http' | |
require 'json' | |
require 'pathname' | |
options = OpenStruct.new( | |
base: 'origin/master', | |
dev_up: false, | |
chrome: false, | |
refresh: true, | |
open: true, | |
tmux: false, | |
) | |
GITHUB_PR_REGEX = %r{https://github.com/([\w_-]+)/([\w_-]+)/pull/(\d+)} | |
REMOTE_BRANCH_SPLIT = %r{[:|\/]} | |
def remote_branch(string) | |
return ['origin', string] unless string =~ REMOTE_BRANCH_SPLIT | |
remote_suggested_name, remote_branch = string.split(REMOTE_BRANCH_SPLIT) | |
# This is when a URL is passed and you get something like Shopify/shopify, but | |
# you really want origin--but you also want to support forks. The more elegant | |
# solution to this is probably we can in the API see if a repo is the "root", | |
# which is what we're effectively doing here. | |
if /github\.com:#{remote_suggested_name}/i.match?(%x(git remote -v | grep origin)) | |
['origin', remote_branch] | |
else | |
[remote_suggested_name, remote_branch] | |
end | |
end | |
def repo | |
%x(basename $(git rev-parse --show-toplevel)).strip | |
end | |
def repo_path | |
%x(git rev-parse --show-toplevel).strip | |
end | |
def ensure_remote(remote) | |
unless %x(git remote)[remote] | |
repo_git = "git@github.com:#{remote}/#{repo}" | |
%x(git remote add #{remote.inspect} #{repo_git}) | |
end | |
end | |
def pull(branch, remote) | |
system("git fetch #{remote} #{branch}") | |
end | |
def checkout(branch, remote) | |
local_branch_name = "#{remote}-#{branch}" | |
local_branch_name = branch if remote == 'origin' | |
system("git checkout -b #{local_branch_name.inspect} --track #{remote}/#{branch}") | |
end | |
def rebase(branch, remote) | |
system("git pull --rebase #{remote} #{branch}") | |
end | |
def current_branch_tracking_remote | |
rev_parse = %x(git rev-parse HEAD --symbolic-full-name @{u}) | |
rev_parse.match(%r{refs/remotes/(.+)$})[1] | |
end | |
def remote_from_url(url) | |
_, org, repo, pr = url.match(GITHUB_PR_REGEX).to_a | |
http = Net::HTTP.new("api.github.com", 443) | |
http.use_ssl = true | |
request = Net::HTTP::Post.new("/graphql") | |
request["Authorization"] = "bearer #{ENV['GITHUB_ACCESS_TOKEN']}" | |
query = <<~EOF | |
{ | |
"query": "\ | |
query { \ | |
repository(owner:\\"#{org}\\", name:\\"#{repo}\\") { \ | |
pullRequest(number: #{pr}) { \ | |
headRefName \ | |
isCrossRepository \ | |
headRepository { \ | |
owner { \ | |
login \ | |
} \ | |
} \ | |
} \ | |
} \ | |
} \ | |
" | |
} | |
EOF | |
request.body = query | |
$stderr.puts "Finding upstream from URL..." | |
response = http.request(request) | |
pr = JSON.parse(response.body)["data"]["repository"]["pullRequest"] | |
pr["headRefName"] unless pr["isCrossRepository"] | |
"#{pr['headRepository']['owner']['login']}/#{pr['headRefName']}" | |
end | |
OptionParser.new do |opts| | |
opts.banner = <<~EOF | |
Usage: review [OPTIONS] [BRANCH] | |
review is a tool to make it easier to review code in your editor instead of | |
in the Github diff. I find that it puts you in the right state of mind to review | |
code. It makes it much easier to dive into the referenced code and see the connections. | |
It is highly recommended to run their tests as well, and try to break them. | |
Dependencies: | |
* `vim` with the gitgutter plugin to show the diff in the editor. | |
* `chrome-cli` if you want to use the `-c` option. | |
* `GITHUB_ACCESS_TOKEN` set in your environment create one here https://github.com/settings/tokens | |
Flags: | |
EOF | |
opts.on('-b', '--base BRANCH', 'Base to compare (default: origin/master)') do |value| | |
options.base = value | |
end | |
opts.on('-u', '--dev-up', 'Run dev up (default: no)') do | |
options.dev_up = true | |
end | |
opts.on('-t', '--tmux', 'Create new tmux window for review (default: no)') do | |
options.tmux = true | |
end | |
opts.on('-c', '--chrome', 'Get content to review from Chrome\'s current tab (default: no)') do | |
options.chrome = true | |
end | |
opts.on('-r', '--no-refresh', 'Don\'t fetch latest upstream changes (default: yes)') do | |
options.refresh = false | |
end | |
opts.on('-o', '--no-open', 'Don\'t open editor with changes (default: yes)') do | |
options.open = false | |
end | |
opts.on('-c', '--changed-files', 'List changed files (implies -ro)') do | |
options.changed_files = true | |
options.refresh = false | |
options.open = false | |
end | |
end.parse! | |
unless Pathname.new('.git').exist? | |
$stderr.puts 'You are not in a github repository.' | |
exit(1) | |
end | |
remote = ARGV[0] | |
if options.chrome | |
remote = %x{chrome-cli info}[GITHUB_PR_REGEX] | |
raise 'Unable to find a Github pull request in current Chrome tab' | |
end | |
remote = remote_from_url(remote) if remote&.start_with?('https://') | |
remote ||= current_branch_tracking_remote | |
base_remote, base_branch = remote_branch(options.base) | |
target_remote, target_branch = remote_branch(remote) | |
review_diff = "#{base_remote}/#{base_branch}...#{target_remote}/#{target_branch}" | |
unless options.changed_files | |
$stderr.puts "Reviewing #{review_diff}" | |
end | |
if options.refresh | |
ensure_remote(base_remote) | |
pull(base_branch, base_remote) | |
ensure_remote(target_remote) | |
pull(target_branch, target_remote) | |
checkout(target_branch, target_remote) | |
rebase(target_branch, target_remote) | |
end | |
if options.tmux | |
system("tmux new-window -n \"review-#{remote}\" -c \"#{repo_path}\" \"bash --rcfile <(echo '. ~/.bashrc && review -r #{ARGV[0]}')\" \\; split-window -h -c \"#{repo_path}\" \"bash --rcfile <(echo '. ~/.bashrc && dev up')\"") | |
else | |
system('dev up') if options.dev_up | |
vim_command = "nvim -c \"let g:gitgutter_diff_base = '#{base_remote}/#{base_branch}'\" -c \":e!\" $(git diff --name-only #{review_diff})" | |
system(vim_command) if options.open | |
end | |
puts %x(git diff --name-only #{review_diff}) if options.changed_files |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment