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