lib/git/status.rb
module Git
#
# A class for git status
#
class Status
include Enumerable
def initialize(base)
@base = base
construct_status
end
#
# Returns an Enumerable containing files that have changed from the
# git base directory
#
# @return [Enumerable]
def changed
@files.select { |_k, f| f.type == 'M' }
end
#
# Determines whether the given file has been changed.
# File path starts at git base directory
#
# @param file [String] The name of the file.
# @example Check if lib/git.rb has changed.
# changed?('lib/git.rb')
# @return [Boolean]
def changed?(file)
changed.member?(file)
end
#
# Returns an Enumerable containing files that have been added.
# File path starts at git base directory
#
# @return [Enumerable]
def added
@files.select { |_k, f| f.type == 'A' }
end
#
# Determines whether the given file has been added to the repository
# File path starts at git base directory
#
# @param file [String] The name of the file.
# @example Check if lib/git.rb is added.
# added?('lib/git.rb')
# @return [Boolean]
def added?(file)
added.member?(file)
end
#
# Returns an Enumerable containing files that have been deleted.
# File path starts at git base directory
#
# @return [Enumerable]
def deleted
@files.select { |_k, f| f.type == 'D' }
end
#
# Determines whether the given file has been deleted from the repository
# File path starts at git base directory
#
# @param file [String] The name of the file.
# @example Check if lib/git.rb is deleted.
# deleted?('lib/git.rb')
# @return [Boolean]
def deleted?(file)
deleted.member?(file)
end
#
# Returns an Enumerable containing files that are not tracked in git.
# File path starts at git base directory
#
# @return [Enumerable]
def untracked
@files.select { |_k, f| f.untracked }
end
#
# Determines whether the given file has is tracked by git.
# File path starts at git base directory
#
# @param file [String] The name of the file.
# @example Check if lib/git.rb is an untracked file.
# untracked?('lib/git.rb')
# @return [Boolean]
def untracked?(file)
untracked.member?(file)
end
def pretty
out = ''
each do |file|
out << pretty_file(file)
end
out << "\n"
out
end
def pretty_file(file)
<<~FILE
#{file.path}
\tsha(r) #{file.sha_repo} #{file.mode_repo}
\tsha(i) #{file.sha_index} #{file.mode_index}
\ttype #{file.type}
\tstage #{file.stage}
\tuntrac #{file.untracked}
FILE
end
# enumerable method
def [](file)
@files[file]
end
def each(&block)
@files.values.each(&block)
end
# subclass that does heavy lifting
class StatusFile
attr_accessor :path, :type, :stage, :untracked
attr_accessor :mode_index, :mode_repo
attr_accessor :sha_index, :sha_repo
def initialize(base, hash)
@base = base
@path = hash[:path]
@type = hash[:type]
@stage = hash[:stage]
@mode_index = hash[:mode_index]
@mode_repo = hash[:mode_repo]
@sha_index = hash[:sha_index]
@sha_repo = hash[:sha_repo]
@untracked = hash[:untracked]
end
def blob(type = :index)
if type == :repo
@base.object(@sha_repo)
else
begin
@base.object(@sha_index)
rescue
@base.object(@sha_repo)
end
end
end
end
private
def construct_status
@files = @base.lib.ls_files
fetch_untracked
fetch_modified
fetch_added
@files.each do |k, file_hash|
@files[k] = StatusFile.new(@base, file_hash)
end
end
def fetch_untracked
ignore = @base.lib.ignored_files
root_dir = @base.dir.path
Dir.glob('**/*', File::FNM_DOTMATCH, base: root_dir) do |file|
next if @files[file] || File.directory?(File.join(root_dir, file)) ||
ignore.include?(file) || file =~ %r{^.git\/.+}
@files[file] = { path: file, untracked: true }
end
end
def fetch_modified
# find modified in tree
@base.lib.diff_files.each do |path, data|
@files[path] ? @files[path].merge!(data) : @files[path] = data
end
end
def fetch_added
# find added but not committed - new files
@base.lib.diff_index('HEAD').each do |path, data|
@files[path] ? @files[path].merge!(data) : @files[path] = data
end
end
end
end