bin/gitstatus.rb in git_helpers-0.1.0 vs bin/gitstatus.rb in git_helpers-0.2

- old
+ new

@@ -1,348 +1,117 @@ #!/usr/bin/env ruby -# Inspired by https://github.com/olivierverdier/zsh-git-prompt -# [commit: 350be32093d0585f395413253536d891c247f538, -# last commit checked: 0a6c8b610e799040b612db8888945f502a2ddd9d (2016-02-14)) -#Inspired by the contrib git script -require "open3" -require "pathname" -require "shellwords" -require "optparse" -require "simplecolor" -#require "dr/git" #TODO merge the two implems -SimpleColor.mix_in_string +require "git_helpers" +require 'optparse' -module GitStatus - module Run - extend(self) - #if we get interrupted once, we don't want to launch any more commands - @interrupted=false - def runstatus(*args) - if !@interrupted - begin - if Open3.respond_to?(:capture3) then - out, error, status=Open3.capture3(*args) - return out, status.success? - else - out = `#{args} 2>/dev/null` - status=$? - return out, status.success? - end - rescue Interrupt #interruption - @interrupted=true - return "", false - end - else - return "", false - end - end - def run(*args) - msg,_=runstatus(*args) - return msg - end - end - - class Git - include GitStatus::Run - attr_reader :msg - - def git? - if @git.nil? - _,@git=runstatus "git rev-parse" - end - return @git - end - def getgitdir - return Pathname.new((run "git rev-parse --git-dir").chomp) - end - def ingitdir? - return (run "git rev-parse --is-inside-git-dir") == "true\n" - end - def worktree? - return (run "git rev-parse --is-inside-work-tree") == "true\n" - end - def bare? - return (run "git rev-parse --is-bare-repository") == "true\n" - end - - def cd_and_exec(*args) - if @path.nil? then - yield(*args) - else - if File.directory?(@path) - Dir.chdir(@path) do - yield(*args) - end - else - warn "#{@path} is not a directory" - end - end - end - - def initialize(path=nil) - #a nil path means we want information on the current directory - if !path.nil? - @path=Pathname.new(path).expand_path - end - cd_and_exec {git?} - end - - def get_msg - #gitst="git status --porcelain --branch" - #too many git are too old to mix --porcelain with --branch - gitm=git="git" - gitm="#{git} -c color.ui=always" if $opts[:color] - gitm="#{gitm} status --short --branch" - @msg=run(gitm) - end - - def describe_detached_head - case $opts[:describe] - when "sha1" - describe=(run "git rev-parse --short HEAD").chomp - when "describe" - describe=(run "git describe HEAD").chomp - when "contains" - describe=(run "git describe --contains HEAD").chomp - when "branch" - describe=(run "git describe --contains --all HEAD").chomp - when "match" - describe=(run "git describe --tags --exact-match HEAD").chomp - when "all" #try --contains all, then --all - describe=(run "git describe --contains --all HEAD").chomp - describe=(run "git describe --all HEAD").chomp if describe.nil? or describe.empty? - when "magic" - describe1=(run "git describe --contains --all HEAD").chomp - describe2=(run "git describe --all HEAD").chomp - describe= describe1.length < describe2.length ? describe1 : describe2 - describe=describe1 if describe2.empty? - describe=describe2 if describe1.empty? - else - describe=(run($opts[:describe])).chomp - end - if describe.empty? - describe=(run "git rev-parse --short HEAD").chomp - end - @branch=":#{describe}" - end - - def parse_head(head) - @ahead=@behind=0 - if (head =~ /## Initial commit on (\S*)/) then - @branch=$1 - if @branch =~ /(\S*)\.\.\./ - @branch=$1 - end - @branch+="…" - elsif (head =~ /## (\S*) \(no branch\)/) then - describe_detached_head - elsif (head =~ /## (\S*)(.*)/) then - branchs=$1 - rest=$2 - if (branchs =~ /(\S*)\.\.\.(\S*)/) then - @branch=$1 - remote=$2 - else - @branch=branchs - end - if (rest =~ /.*\[ahead\s+(\d*)(.*)/) then - @ahead=$1.to_i - rest=$2 - end - if (rest =~ /.*behind\s+(\d*)\]/) then - @behind=$1.to_i - end - end - end - - def parse_msg - msg=@msg - #find the branch name, and if we are behind/ahead of upstream - lines=msg.uncolor.lines.to_a - return if lines.empty? - head=lines.shift - parse_head(head) - - #get status of files - @changed=@staged=@untracked=@conflicts=0 - lines.each do |line| - index = line[0]; - workdir = line[1]; - #puts "index: #{index}, workdir: #{workdir}" - if index=~/[DRAMTC]/ then - @staged+=1 - end - if workdir=~ /[DMT]/ then - @changed+=1 - end - if workdir=='?' || index=='?' then - @untracked+=1 - end - if workdir=='U' || index=='U' then - @conflicts+=1 - end - end - @clean=true - @clean=false if @staged != 0 || @changed !=0 || - @untracked !=0 || @conflicts !=0 - end - - def get_status - if worktree? - get_msg - parse_msg - if $opts[:sequencer] and !@msg.empty? - @sequencer="" - gitdir=getgitdir - if (gitdir+"rebase-merge").directory? - if (gitdir+"rebase-merge/interactive").file? - @sequencer<<" rb-i " #REBASE-i - else - @sequencer<<" rb-m " #REBASE-m - end - @sequencer<<(gitdir+"rebase-merge/head-name").read.chomp.sub(/^refs\/heads\//,"") - end - if (gitdir+"rebase-apply").directory? - if (gitdir+"rebase-apply/rebasing").file? - @sequencer<<" rb" #RB - elsif (gitdir+"rebase-apply/applying").file? - @sequencer<<" am" #AM - else - @sequencer<<" am/rb" #AM/REBASE - end - end - if (gitdir+"MERGE_HEAD").file? - @sequencer<<" mg" #MERGING - end - if (gitdir+"CHERRY_PICK_HEAD").file? - @sequencer<<" ch" #CHERRY-PICKING - end - if (gitdir+"BISECT_LOG").file? - @sequencer<<" bi" #BISECTING - end - _,stashstatus=runstatus "git rev-parse --verify refs/stash" - if stashstatus - stashs=run "git rev-list -g refs/stash" - @sequencer<<" $#{stashs.lines.to_a.length}" #Stash - end - end - return !@msg.empty? - else - if $opts[:sequencer] - if ingitdir? - if bare? - @branch="|bare|" - else - @branch="|.git|" - end - end - end - end - return false - end - - def status - cd_and_exec { get_status } if git? - end - - def prompt - if status - return "(" << - @branch.color(:magenta,:bold) << - (@ahead==0 ? "" : "↑"<<@ahead.to_s ) << - (@behind==0 ? "" : "↓"<<@behind.to_s ) << - "|" << - (@staged==0 ? "" : ("●"+@staged.to_s).color(:red) ) << - (@conflicts==0 ? "" : ("✖"+@conflicts.to_s).color(:red) ) << - (@changed==0 ? "" : ("✚"+@changed.to_s).color(:blue) ) << - (@untracked==0 ? "" : "…" ) << - (@clean ? "✔".color(:green,:bold) : "" ) << - (@sequencer.empty? ? "" : @sequencer.color(:yellow) ) << - ")" - else - return "(" << @branch.color(:magenta,:bold) << ")" if @branch - end - end - - def porcelain - if git? - return "#{@branch}\n#{@ahead}\n#{@behind}\n#{@staged}\n#{@conflicts}\n#{@changed}\n#{@untracked}\n#{@clean?1:0}\n#{@sequencer}\n" - else - return "" - end - end - end -end - -$opts={:color => true, :indent => nil, :sequencer => true, :describe => "magic"} +opts={:color => true} optparse = OptionParser.new do |opt| opt.banner= "#{File.basename($0)} [options] git_dirs" opt.on("-p", "--[no-]prompt", "To be used in shell prompt", "This ensure that color ansi sequence are escaped so that they are not counted as text by the shell") do |v| - $opts[:prompt]=v + opts[:prompt]=v + opts[:max_length]=40 if v end - opt.on("--[no-]porcelain", "Don't format the status but output it in a machine convenient format") do |v| - $opts[:porcelain]=v + opt.on("-s", "--[no-]status[=options]", "List file", "Print the output of git status additionally of what this program parse") do |v| + opts[:status]=v end - opt.on("-s", "--[no-]status", "List file", "Print the output of git status additionally of what this program parse") do |v| - $opts[:status]=v - end opt.on("-c", "--[no-]color", "Color output", "on by default") do |v| - $opts[:color]=v + opts[:color]=v end opt.on("--[no-]sequencer", "Show sequencer data (and also look for bare directory)", "on by default") do |v| - $opts[:sequencer]=v + opts[:sequencer]=v end opt.on("--indent spaces", Integer, "Indent to use if showing git status", "2 by default, 0 for empty ARGV") do |v| - $opts[:indent]=v + opts[:indent]=v end - opt.on("--describe sha1/describe/contains/branch/match/all/magic", "How to describe a detached HEAD", "'magic' by default") do |v| - $opts[:describe]=v + opt.on("--describe sha1/describe/contains/branch/match/all/magic", "How to describe a detached HEAD", "'branch-fb' by default") do |v| + opts[:detached_name]=v end + opt.on("--[no-]ignored[=full]", "-i", "Show ignored files") do |v| + opts[:ignored]=v + end + opt.on("--[no-]untracked[=full]", "-u", "Show untracked files") do |v| + opts[:untracked]=v + end + opt.on("--[no-]branch", "Get branch infos (true by default)") do |v| + opts[:branch]=v + end + opt.on("--[no-]files", "Get files infos (true by default)") do |v| + opts[:files]=v + end + opt.on("--use=branch_name", "Show a different branch than HEAD") do |v| + opts[:use]=v + end + opt.on("--[no-]raw", "Show raw status infos") do |v| + opts[:raw]=v + end opt.on("--sm", "Recurse on each submodules") do |v| - $opts[:submodules]=v + opts[:submodules]=v end + opt.on("--max-length=length", "Maximum status length", Integer) do |v| + opts[:max_length]=v + end + opt.on("--[no-]debug", "Debug git calls") do |v| + opts[:debug]=v + end end optparse.parse! -if !$opts[:color] +if !opts[:color] SimpleColor.enabled=false end +if opts[:debug] + SH.debug +end def prettify_dir(dir) - return dir.sub(/^#{ENV['HOME']}/,"~") + return '' if dir.nil? + return (dir.sub(/^#{ENV['HOME']}/,"~"))+": " end -def gs_output(dir) - g=GitStatus::Git.new(dir) - puts "#{prettify_dir(dir)+": " if dir}#{g.prompt}" - if $opts[:status] and g.git? - g.msg.lines.each do |line| - print " "*$opts[:indent] + line +def gs_output(dir=".", **opts) + g=GitHelpers::GitDir.new(dir || ".") + status={} + arg=opts.delete(:use) + args= arg.nil? ? [] : [arg] + if opts[:raw] + puts "#{prettify_dir(dir)}#{g.status(*args,**opts)}" + else + puts "#{prettify_dir(dir)}#{g.format_status(*args,**opts) {|s| status=s}}" + end + if opts[:status] and g.worktree? + g.with_dir do + options=opts[:status] + if options.is_a?(String) + options=options.split(',') + else + options=[] + end + out=SH.run_simple("git #{opts[:color] ? "-c color.ui=always" : ""} status --short #{(status[:status_options]+options).shelljoin}") + out.each_line.each do |line| + print " "*(opts[:indent]||0) + line + end end end end -if $opts[:porcelain] - puts GitStatus::Git.new.porcelain -elsif $opts[:prompt] +if opts[:prompt] SimpleColor.enabled=:shell - prompt=GitStatus::Git.new.prompt + prompt=GitHelpers.create.format_status(**opts) puts prompt if prompt #in ruby1.8, puts nil output nil... else args=ARGV if args.empty? - $opts[:indent]=0 unless $opts[:indent] + opts[:indent]=0 unless opts[:indent] args=[nil] else - $opts[:indent]=2 unless $opts[:indent] + opts[:indent]=2 unless opts[:indent] end args.each do |dir| - gs_output(dir) - if $opts[:submodules] - Dir.chdir(dir||".") do - %x/git submodule status/.each_line.map { |l| l.split[1] }.each do |dir| - gs_output(dir) + gs_output(dir,**opts) + if opts[:submodules] + g.with_dir do + %x/git submodule status/.each_line.map { |l| l.split[1] }.each do |sdir| + gs_output(sdir, **opts) end end end end end