require 'cmds' require 'nrser/refinements/types' using NRSER::Types module QB module Repo # Encapsulate information about a Git repository and expose useful operations as # instance methods. # # The main entry point is {QB::Repo::Git.from_path}, which creates a # class Git < NRSER::Meta::Props::Base GITHUB_SSH_URL_RE = /^git@github\.com\:(?<owner>.*)\/(?<name>.*)\.git$/ GITHUB_HTTPS_URL_RE = /^https:\/\/github\.com\/(?<owner>.*)\/(?<name>.*)\.git$/ class User < NRSER::Meta::Props::Base prop :name, type: t.maybe(t.str), default: nil prop :email, type: t.maybe(t.str), default: nil end # class GitHubRemote < NRSER::Meta::Props::Base # SSH_URL_RE = /^git@github\.com\:(?<owner>.*)\/(?<name>.*)\.git$/ # HTTPS_URL_RE = /^https:\/\/github\.com\/(?<owner>.*)\/(?<name>.*)\.git$/ # # prop :owner, type: t.str # prop :name, type: t.str # prop :api_response, type: t.maybe(t.hash) # # prop :full_name, type: t.str, source: :full_name # # # # Class Methods # # ===================================================================== # # # Test if a Git SSH or HTTPS remote url points to GitHub. # # # # @param [String] url # # # # @return [Boolean] # # # def self.url? url # SSH_URL_RE.match(url) || HTTPS_URL_RE.match(url) # end # .url? # # # # Instantiate an instance from a Git SSH or HTTPS remote url that points # # to GitHub. # # # # @param [type] arg_name # # @todo Add name param description. # # # # @return [QB::Repo::Git::GitHubRemote] # # @todo Document return value. # # # def self.from_url url, use_api: false # match = SSH_URL_RE.match(git.origin) || # HTTPS_URL_RE.match(git.origin) # # unless match # raise ArgumentError.new NRSER.squish <<-END # url #{ url.inspect } does not match GitHub SSH or HTTPS patterns. # END # end # # owner = match['owner'] # name = match['name'] # # if use_api # # end # end # .from_url # # # # # @todo Document full_name method. # # # # @param [type] arg_name # # @todo Add name param description. # # # # @return [return_type] # # @todo Document return value. # # # def full_name arg_name # "#{ owner }/#{ name }" # end # #full_name # # end # Class Methods # ===================================================================== # @todo Document from_path method. # # @param [String, Pathname] input_path # A path that is in the Git repo. # # @return [QB::Repo::Git] # # @raise [QB::FSStateError] # - If we can't find any existing directory to look in based on # `input_path`. # # - If the directory we do find to look in does not seems to be part of # a Git repo. # def self.from_path path, use_github_api: false raw_input_path = path # Create a Pathname from the input input_path = Pathname.new raw_input_path # input_path may point to a file, or something that doesn't even exist. # We want to ascend until we find an existing directory that we can cd into. closest_dir = input_path.ascend.find {|p| p.directory?} # Make sure we found something if closest_dir.nil? raise QB::FSStateError, "Unable to find any existing directory from path " + "#{ raw_input_path.inspect }" end # Change into the directory to make shell life easier Dir.chdir closest_dir do root_result = Cmds.capture "git rev-parse --show-toplevel" unless root_result.ok? raise QB::FSStateError, "Path #{ raw_input_path.inspect } does not appear to be in a " + "Git repo (looked in #{ closest_dir.inspect })." end root = Pathname.new root_result.out.chomp user = User.new **NRSER.map_values(User.props.keys) {|key, _| begin Cmds.chomp! "git config user.#{ key }" rescue end } is_clean = Cmds.chomp!('git status --porcelain 2>/dev/null').empty? rev_parse = Cmds.capture 'git rev-parse HEAD' head = if rev_parse.ok? rev_parse.out.chomp end branch_result = Cmds.capture 'git branch --no-color 2> /dev/null' branch = if branch_result.ok? if line = branch_result.out.lines.find {|line| line.start_with? '*'} if m = line.match(/\*\s+(\S+)/) m[1] end end end origin = begin Cmds.chomp! "git remote get-url origin" rescue end owner = nil name = nil github = nil if origin && match = ( GITHUB_SSH_URL_RE.match(origin) || GITHUB_HTTPS_URL_RE.match(origin) ) owner = match['owner'] name = match['name'] if use_github_api github = OpenStruct.new github.api_url = "https://api.github.com/repos/#{ owner }/#{ name }" response = Net::HTTP.get_response URI(github.api_url) if response.is_a? Net::HTTPSuccess # parse response body and add everything to github result parsed = JSON.parse response.body parsed.each {|k, v| github[k] = v} else # assume it's private if we failed to find it github.private = true end github = github.to_h end end new( raw_input_path: raw_input_path, input_path: input_path, root: root, user: user, is_clean: is_clean, head: head, branch: branch, origin: origin, owner: owner, name: name, github: github, ) end # chdir end # .from_path # Props # ===================================================================== prop :raw_input_path, type: t.maybe(t.union(Pathname, t.str)), default: nil prop :root, type: Pathname prop :user, type: User prop :is_clean, type: t.bool prop :head, type: t.maybe(t.str), default: nil prop :branch, type: t.maybe(t.str), default: nil prop :origin, type: t.maybe(t.str), default: nil prop :owner, type: t.maybe(t.str), default: nil prop :name, type: t.maybe(t.str), default: nil prop :github, type: t.maybe(t.hash_), default: nil prop :head_short, type: t.maybe(t.str), source: :head_short prop :full_name, type: t.maybe(t.str), source: :full_name prop :is_github, type: t.bool, source: :github? # Instance Methods # ===================================================================== def full_name "#{ owner }/#{ name }" if owner && name end def head_short head[0...7] if head end def github? !github.nil? end def clean? is_clean end end # class Git end # module Repo end # module QB