module Ninny class Git extend Forwardable NO_BRANCH = "(no branch)" DEFAULT_DIRTY_MESSAGE = "Your Git index is not clean. Commit, stash, or otherwise clean up the index before continuing." DIRTY_CONFIRM_MESSAGE = "Your Git index is not clean. Do you want to continue?" # branch prefixes DEPLOYABLE_PREFIX = "deployable" STAGING_PREFIX = "staging" QAREADY_PREFIX = "qaready" def_delegators :git, :branch attr_reader :git def initialize @git = ::Git.open(Dir.pwd) end def command(*args) git.lib.send(:command, *args) end def current_branch git.branch(current_branch_name) end def current_branch_name name = git.current_branch if name == NO_BRANCH raise NotOnBranch, "Not currently checked out to a particular branch" else name end end def merge(branch_name) if_clean do git.fetch current_branch.merge("origin/#{branch_name}") raise MergeFailed unless clean? push end end # Public: Push the current branch to GitHub def push if_clean do git.push('origin', current_branch_name) end end # Public: Pull the latest changes for the checked-out branch def pull if_clean do command('pull') end end # Public: Create a new branch from the given source # # new_branch_name - The name of the branch to create # source_branch_name - The name of the branch to branch from def new_branch(new_branch_name, source_branch_name) git.fetch command('branch', ['--no-track', new_branch_name, "origin/#{source_branch_name}"]) new_branch = branch(new_branch_name) new_branch.checkout command('push', ['-u', 'origin', new_branch_name]) end # Public: Delete the given branch # # branch_name - The name of the branch to delete def delete_branch(branch_name) branch = branch_name.is_a?(::Git::Branch) ? branch_name : git.branch(branch_name) git.push('origin', ":#{branch}") branch.delete end # Public: The list of branches on GitHub # # Returns an Array of Strings containing the branch names def remote_branches git.fetch git.branches.remote.map{ |branch| git.branch(branch.name) }.sort_by(&:name) end # Public: List of branches starting with the given string # # prefix - String to match branch names against # # Returns an Array of Branches containing the branch name def branches_for(prefix) remote_branches.select do |branch| branch.name =~ /^#{prefix}/ end end # Public: Most recent branch starting with the given string # # prefix - String to match branch names against # # Returns an Array of Branches containing the branch name def latest_branch_for(prefix) branches_for(prefix).last || raise(NoBranchOfType, "No #{prefix} branch") end # Public: Whether the Git index is clean (has no uncommited changes) # # Returns a Boolean def clean? command('status', '--short').empty? end # Public: Perform the block if the Git index is clean def if_clean(message=DEFAULT_DIRTY_MESSAGE) if clean? || prompt.yes?(DIRTY_CONFIRM_MESSAGE) yield else alert_dirty_index message exit 1 end end # Public: Display the message and show the git status def alert_dirty_index(message) prompt.say " " prompt.say message prompt.say " " prompt.say command('status') raise DirtyIndex end def prompt(**options) require 'tty-prompt' TTY::Prompt.new(options) end # Exceptions NotOnBranch = Class.new(StandardError) NoBranchOfType = Class.new(StandardError) DirtyIndex = Class.new(StandardError) end end