Skip to content

Instantly share code, notes, and snippets.

@felix-d
Created April 27, 2020 13:12
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 felix-d/629d690b44feb1737d381eda6fad3946 to your computer and use it in GitHub Desktop.
Save felix-d/629d690b44feb1737d381eda6fad3946 to your computer and use it in GitHub Desktop.
Review git branches
#!/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