fortinmike/git-multirepo

View on GitHub
lib/multirepo/logic/merge-descriptor.rb

Summary

Maintainability
A
1 hr
Test Coverage
require "colored"
require "multirepo/git/repo"

module MultiRepo
  class TheirState
    NON_EXISTENT = 0
    EXACT_REF = 1
    LOCAL_NO_UPSTREAM = 2
    UPSTREAM_NO_LOCAL = 3
    LOCAL_UP_TO_DATE = 4
    LOCAL_OUTDATED = 5
    LOCAL_UPSTREAM_DIVERGED = 6
  end
  
  class MergeDescriptor
    attr_accessor :name
    attr_accessor :repo
    attr_accessor :our_revision
    attr_accessor :their_revision
    attr_accessor :state

    def initialize(name, repo, our_revision, their_revision)
      @name = name
      @repo = repo
      @our_revision = our_revision
      @their_revision = their_revision
      
      # Revisions can be anything: "feature1", "origin/feature1", "b51f3c0", ...
      their_ref = repo.ref(their_revision)
      
      @short_commit_id = their_ref.short_commit_id
      
      @state = determine_merge_state(repo, their_ref)
    end
    
    def merge_description
      case @state
      when TheirState::NON_EXISTENT then "No revision named #{@their_revision}".red
      else; "Merge '#{@state == TheirState::EXACT_REF ? @short_commit_id : @their_revision}' into '#{@our_revision}'"
      end
    end

    def upstream_description
      case @state
      when TheirState::NON_EXISTENT then "--"
      when TheirState::EXACT_REF then "Exact ref".yellow
      when TheirState::LOCAL_NO_UPSTREAM then "Not remote-tracking".yellow
      when TheirState::UPSTREAM_NO_LOCAL then "Branch is upstream".green
      when TheirState::LOCAL_UP_TO_DATE then "Local up-to-date with upstream".green
      when TheirState::LOCAL_OUTDATED then "Local outdated compared to upstream".yellow
      when TheirState::LOCAL_UPSTREAM_DIVERGED then "Local and upstream have diverged!".red
      end
    end
    
    private
    
    def determine_merge_state(repo, their_ref)
      return TheirState::NON_EXISTENT unless their_ref.exists?
      
      remote_branch = repo.remote_branches.find { |b| b.name == their_ref.name }
      local_branch = repo.local_branches.find { |b| b.name == their_ref.name }
      
      # If no local branch nor remote branch exist for their_ref, this is an exact ref
      return TheirState::EXACT_REF unless remote_branch || local_branch
      
      # If remote exists but local does not, return UPSTREAM_NO_LOCAL
      return TheirState::UPSTREAM_NO_LOCAL if remote_branch && !local_branch
      
      # If there is no upstream, no need to check for differences between local and remote
      return TheirState::LOCAL_NO_UPSTREAM unless local_branch.upstream_branch
      
      # Else check local vs upstream state
      return determine_local_upstream_merge_state(repo, their_ref)
    end
    
    def determine_local_upstream_merge_state(repo, their_ref)
      # We can assume we're working with a branch at this point
      their_branch = repo.branch(their_ref.name)
      
      their_upstream_branch = their_branch.upstream_branch
      local_as_upstream = their_branch.commit_id == their_upstream_branch.commit_id
      can_fast_forward_local_to_upstream = their_branch.can_fast_forward_to?(their_upstream_branch)
      
      state = if local_as_upstream
        TheirState::LOCAL_UP_TO_DATE
      elsif !local_as_upstream && can_fast_forward_local_to_upstream
        TheirState::LOCAL_OUTDATED
      else
        TheirState::LOCAL_UPSTREAM_DIVERGED
      end
      
      return state
    end
  end
end