require 'grit' require 'pathname' require_relative 'repo/version' module Hillary class Repo DirtyProjectError = Class.new(StandardError) MissingHeadError = Class.new(StandardError) PRODUCTION_TAG_FORMAT = "%Y_%m_%d_%H_%M_%S" RC_TAG_FORMAT = "RC#{PRODUCTION_TAG_FORMAT}" PRODUCTION_TAG_REGEX = /^\d{4}(_\d{2}){5}$/ RC_TAG_REGEX = /^RC\d{4}(_\d{2}){5}$/ attr_reader :name, :logger class << self def rc_tag_name DateTime.now.strftime(RC_TAG_FORMAT) end def production_tag_name DateTime.now.strftime(PRODUCTION_TAG_FORMAT) end end def initialize(dir, logger = Logger.new($stdout)) @name = Pathname.new(File.expand_path(dir)).basename @repo = Grit::Repo.new(dir) @logger = logger logger.formatter = proc{|_, _, _, msg| msg + "\n"} end def head repo.head end def checkout(ref) raise DirtyProjectError, "Cannot checkout '#{ref}', #{name} is dirty." if dirty? logger.info "Checking out #{ref} and updating from origin" if !repo.objects([ref]).empty? git.checkout({}, ref) else raise MissingHeadError, "Cannot checkout '#{ref}', because it doesn't exist." end git.pull({}, 'origin', ref) end def create_rc_tag(branch_name = 'master', tag_name = self.class.rc_tag_name) branch = repo.remotes.find{|branch| branch.name == "origin/#{branch_name}"} tag_and_push(tag_name, branch) end def create_production_tag(tag_name = self.class.production_tag_name) rc_tag = last_rc_tag tag_and_push(tag_name, rc_tag) end def tag_and_push(tag_name, ref, message = tag_name) ref_name = ref.respond_to?(:name) ? ref.name : ref commit = ref.respond_to?(:commit) ? ref.commit : ref sha = commit.respond_to?(:sha) ? commit.sha : ref git.fetch logger.info "Tagging #{ref_name} (#{sha}) as #{tag_name}" git.tag({a: tag_name, m: message}, sha) push(tag_name) end def delete_last_rc_tag delete_tag_and_push(last_rc_tag.name) end def delete_last_production_tag delete_tag_and_push(last_production_tag.name) end def delete_tag_and_push(tag_name) git.fetch logger.info "Deleting #{tag_name} locally" git.tag({d: tag_name}) push(":refs/tags/#{tag_name}") end def commit_and_push(files, message) logger.info "Adding files:", *files.map{|f| " #{f}"} git.add({}, files) logger.info "Committing with '#{message}'" git.commit({m: message}) logger.info "Pushing changes to origin/head" push('head') end def last_rc_tag repo.tags.select{|tag| tag.name =~ RC_TAG_REGEX}.sort_by(&:name).last end def last_production_tag repo.tags.select{|tag| tag.name =~ PRODUCTION_TAG_REGEX}.sort_by(&:name).last end private attr_reader :repo def push(ref) flags = if ref[/^:/] logger.info "Deleting #{ref} on origin" {} else logger.info "Pushing #{ref} to origin" {u: true} end git.push(flags, 'origin', ref) end def dirty? [:added, :changed, :deleted].any?{|mod| !status.send(mod).empty?} end def git repo.git end def status repo.status end end end