Created
June 18, 2013 17:02
-
-
Save joeyAghion/5807248 to your computer and use it in GitHub Desktop.
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 | |
# I don't remember where I found this, but all credit goes to them. | |
require 'rubygems' | |
gem 'term-ansicolor', '=1.0.3' | |
require 'term/ansicolor' | |
class GitCommit | |
attr_reader :content | |
def initialize(content) | |
@content = content | |
end | |
def sha | |
@content.split[1] | |
end | |
def to_s | |
`git log --pretty=format:"%h %ad %an - %s" #{sha}~1..#{sha}` | |
end | |
def unmerged? | |
content =~ /^\+/ | |
end | |
def equivalent? | |
content =~ /^\-/ | |
end | |
end | |
class GitBranch | |
attr_reader :name, :commits | |
def initialize(name, commits) | |
@name = name | |
@commits = commits | |
end | |
def unmerged_commits | |
commits.select{ |commit| commit.unmerged? } | |
end | |
def equivalent_commits | |
commits.select{ |commit| commit.equivalent? } | |
end | |
end | |
class GitBranches < Array | |
def self.clean_branch_output(str) | |
str.split(/\n/).map{ |e| e.strip.gsub(/\*\s+/, '') }.reject{ |branch| branch =~ /\b#{Regexp.escape(UPSTREAM)}\b/ }.sort | |
end | |
def self.local_branches | |
clean_branch_output `git branch` | |
end | |
def self.remote_branches | |
clean_branch_output `git branch -r` | |
end | |
def self.load(options) | |
git_branches = new | |
branches = if options[:local] | |
local_branches | |
elsif options[:remote] | |
remote_branches | |
end | |
branches.each do |branch| | |
raw_commits = `git cherry -v #{UPSTREAM} #{branch}`.split(/\n/).map{ |c| GitCommit.new(c) } | |
git_branches << GitBranch.new(branch, raw_commits) | |
end | |
git_branches | |
end | |
def unmerged | |
reject{ |branch| branch.commits.empty? }.sort_by{ |branch| branch.name } | |
end | |
def any_missing_commits? | |
select{ |branch| branch.commits.any? }.any? | |
end | |
end | |
class GitUnmerged | |
VERSION = "1.0.1" | |
include Term::ANSIColor | |
attr_reader :branches | |
def initialize(args) | |
@options = {} | |
extract_options_from_args(args) | |
end | |
def load | |
@branches ||= GitBranches.load(:local => local?, :remote => remote?) | |
end | |
def print_overview | |
load | |
if branches.any_missing_commits? | |
puts "The following branches possibly have commits not merged to #{upstream}:" | |
branches.each do |branch| | |
num_unmerged = yellow(branch.unmerged_commits.size.to_s) | |
num_equivalent = green(branch.equivalent_commits.size.to_s) | |
puts %| #{branch.name} (#{num_unmerged}/#{num_equivalent} commits)| | |
end | |
end | |
end | |
def print_help | |
puts <<-EOT.gsub(/^\s+\|/, '') | |
|Usage: #{$0} [-a] [--upstream <branch>] [--remote] | |
| | |
|This script wraps the "git cherry" command. It reports the commits from all local | |
|branches which have not been merged into an upstream branch. | |
| | |
| #{yellow("yellow")} commits have not been merged | |
| #{green("green")} commits have equivalent changes in <upstream> but different SHAs | |
| | |
|The default upstream is 'master'. | |
| | |
|OPTIONS: | |
| -a display all unmerged commits (verbose) | |
| --remote compare remote branches instead of local branches | |
| --upstream <branch> specify a specific upstream branch | |
| | |
|EXAMPLE: check for all unmerged commits | |
| #{$0} | |
| | |
|EXAMPLE: check for all unmerged commits and merged commits (but with a different SHA) | |
| #{$0} -a | |
| | |
|EXAMPLE: use a different upstream than master | |
| #{$0} --upstream otherbranch | |
| | |
|EXAMPLE: compare remote branches against origin/master | |
| #{$0} --remote | |
| | |
|GITCONFIG: | |
| If you name this file git-unmerged and place it somewhere in your PATH | |
| you will be able to type "git unmerged" to use it. If you'd like to name | |
| it something else and still refer to it with "git unmerged" then you'll | |
| need to set up an alias: | |
| git config --global alias.unmerged \\!#{$0} | |
| | |
|Version: #{VERSION} | |
|Author: Zach Dennis <zdennis@mutuallyhuman.com> | |
EOT | |
exit | |
end | |
def print_version | |
puts "#{VERSION}" | |
end | |
def branch_description | |
local? ? "local" : "remote" | |
end | |
def print_specifics | |
load | |
if branches.any_missing_commits? | |
print_breakdown | |
else | |
puts "There are no #{branch_description} branches out of sync with #{upstream}" | |
end | |
end | |
def print_breakdown | |
puts "Below is a breakdown for each branch. Here's a legend:" | |
puts | |
print_legend | |
branches.each do |branch| | |
puts | |
print "#{branch.name}:" | |
if branch.unmerged_commits.empty? && !show_equivalent_commits? | |
print "(no umerged commits, must have merged commits with different SHAs)\n" | |
else | |
puts | |
end | |
branch.unmerged_commits.each { |commit| puts yellow(commit.to_s) } | |
if show_equivalent_commits? | |
branch.equivalent_commits.each do |commit| | |
puts green(commit.to_s) | |
end | |
end | |
end | |
end | |
def print_legend | |
load | |
puts " " + yellow("yellow") + " commits have not been merged" | |
puts " " + green("green") + " commits have equivalent changes in #{UPSTREAM} but different SHAs" if show_equivalent_commits? | |
end | |
def show_help? ; @options[:show_help] ; end | |
def show_equivalent_commits? ; @options[:show_equivalent_commits] ; end | |
def show_version? ; @options[:show_version] ; end | |
def upstream | |
if @options[:upstream] | |
@options[:upstream] | |
elsif local? | |
"master" | |
elsif remote? | |
"origin/master" | |
end | |
end | |
private | |
def extract_options_from_args(args) | |
if args.include?("--remote") | |
@options[:remote] = true | |
else | |
@options[:local] = true | |
end | |
@options[:show_help] = true if args.include?("-h") || args.include?("--help") | |
@options[:show_equivalent_commits] = true if args.include?("-a") | |
@options[:show_version] = true if args.include?("-v") || args.include?("--version") | |
if index=args.index("--upstream") | |
@options[:upstream] = args[index+1] | |
end | |
end | |
def local? ; @options[:local] ; end | |
def remote? ; @options[:remote] ; end | |
end | |
unmerged = GitUnmerged.new ARGV | |
UPSTREAM = unmerged.upstream | |
if unmerged.show_help? | |
unmerged.print_help | |
exit | |
elsif unmerged.show_version? | |
unmerged.print_version | |
exit | |
else | |
unmerged.print_overview | |
puts | |
unmerged.print_specifics | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment