require 'colored' require 'grit' class GitUp def initialize(args) @repo = Grit::Repo.new(File.expand_path(".")) @head = @repo.head end def run system "git fetch" with_stash do returning_to_current_branch do col_width = @repo.branches.map { |b| b.name.length }.max + 1 @repo.branches.each do |branch| next unless remote = remote_for_branch(branch) print branch.name.ljust(col_width) if remote.commit.sha == branch.commit.sha puts "up to date".green next end base = merge_base(branch.name, remote.name) if base == remote.commit.sha puts "ahead of upstream".green next end if base == branch.commit.sha puts "fast-forwarding...".yellow else puts "rebasing...".yellow end checkout(branch.name) rebase(remote) end end end rescue GitError => e puts e.message.red puts "Here's what Git said:".red puts e.output end def remote_for_branch(branch) remote_name = @repo.config["branch.#{branch.name}.remote"] || "origin" @repo.remotes.find { |r| r.name == "#{remote_name}/#{branch.name}" } end def with_stash stashed = false status = @repo.status change_count = status.added.length + status.changed.length + status.deleted.length if change_count > 0 puts "stashing #{change_count} changes".magenta @repo.git.stash stashed = true end yield if stashed puts "unstashing".magenta @repo.git.stash({}, "apply") end end def returning_to_current_branch unless @repo.head.respond_to?(:name) puts "You're not currently on a branch. I'm exiting in case you're in the middle of something.".red return end branch_name = @repo.head.name yield unless on_branch?(branch_name) puts "returning to #{branch_name}".magenta checkout(branch_name) end end def checkout(branch_name) output = @repo.git.checkout({}, branch_name) unless on_branch?(branch_name) raise GitError.new("Failed to checkout #{branch_name}", output) end end def rebase(target_branch) current_branch = @repo.head output, err = @repo.git.sh("#{Grit::Git.git_binary} rebase #{target_branch.name}") unless on_branch?(current_branch.name) and is_fast_forward?(current_branch, target_branch) raise GitError.new("Failed to rebase #{current_branch.name} onto #{target_branch.name}", output+err) end end def is_fast_forward?(a, b) merge_base(a.name, b.name) == b.commit.sha end def merge_base(a, b) @repo.git.send("merge-base", {}, a, b).strip end def on_branch?(branch_name=nil) @repo.head.respond_to?(:name) and @repo.head.name == branch_name end class GitError < StandardError attr_reader :output def initialize(message, output) super(message) @output = output end end end