git_helpers 0.1.0 → 0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ChangeLog.md +99 -2
- data/LICENSE.txt +1 -1
- data/Rakefile +7 -10
- data/bin/diff-fancy.rb +1 -0
- data/bin/gitstatus.old.rb +349 -0
- data/bin/gitstatus.rb +74 -305
- data/lib/git_helpers.rb +24 -330
- data/lib/git_helpers/branch.rb +216 -0
- data/lib/git_helpers/branch_infos.rb +178 -0
- data/lib/git_helpers/diff.rb +701 -0
- data/lib/git_helpers/extra_helpers.rb +24 -220
- data/lib/git_helpers/git_dir.rb +176 -0
- data/lib/git_helpers/raw_helpers.rb +105 -0
- data/lib/git_helpers/stats.rb +183 -0
- data/lib/git_helpers/status.rb +404 -0
- data/lib/git_helpers/submodules.rb +32 -0
- data/lib/git_helpers/version.rb +1 -1
- metadata +13 -3
- data/bin/diff-fancy.rb +0 -699
@@ -0,0 +1,105 @@
|
|
1
|
+
|
2
|
+
module GitHelpers
|
3
|
+
#these raw helpers are not called since we usually use higher level
|
4
|
+
#commands that provide all infos at once
|
5
|
+
class GitDir
|
6
|
+
#are we in a git folder?
|
7
|
+
def raw_git?(quiet: false)
|
8
|
+
launch="git rev-parse"
|
9
|
+
launch=launch + " 2>/dev/null" if quiet
|
10
|
+
with_dir do
|
11
|
+
system launch
|
12
|
+
return DR::Bool.to_bool($?)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
#are we in .git/?
|
17
|
+
def raw_gitdir?
|
18
|
+
with_dir do
|
19
|
+
return DR::Bool.to_bool(%x/git rev-parse --is-inside-git-dir/)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
#are we in the worktree?
|
23
|
+
def raw_worktree?
|
24
|
+
with_dir do
|
25
|
+
return DR::Bool.to_bool(%x/git rev-parse --is-inside-work-tree/)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
#are we in a bare repo?
|
29
|
+
def raw_bare?
|
30
|
+
with_dir do
|
31
|
+
return DR::Bool.to_bool(%x/git rev-parse --is-bare-repository/)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
#return the absolute path of the toplevel
|
36
|
+
def raw_toplevel
|
37
|
+
with_dir do
|
38
|
+
return Pathname.new(%x/git rev-parse --show-toplevel/.chomp)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
#relative path from toplevel to @dir
|
42
|
+
def raw_prefix
|
43
|
+
with_dir do
|
44
|
+
return Pathname.new(%x/git rev-parse --show-prefix/.chomp)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
#return the relative path from @dir to the toplevel
|
48
|
+
def raw_relative_toplevel
|
49
|
+
with_dir do
|
50
|
+
return Pathname.new(%x/git rev-parse --show-cdup/.chomp)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
#get path to .git directory (can be relative or absolute)
|
54
|
+
def raw_gitdir
|
55
|
+
with_dir do
|
56
|
+
return Pathname.new(%x/git rev-parse --git-dir/.chomp)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class GitBranch
|
62
|
+
def raw_rebase?
|
63
|
+
@gitdir.with_dir do
|
64
|
+
rb=%x/git config --bool branch.#{@branch.shellescape}.rebase/.chomp!
|
65
|
+
rb||=%x/git config --bool pull.rebase/.chomp!
|
66
|
+
return rb=="true"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def raw_remote
|
71
|
+
@gitdir.with_dir do
|
72
|
+
rm=%x/git config --get branch.#{@branch.shellescape}.remote/.chomp!
|
73
|
+
rm||="origin"
|
74
|
+
return rm
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def raw_push_remote
|
79
|
+
@gitdir.with_dir do
|
80
|
+
rm= %x/git config --get branch.#{@branch.shellescape}.pushRemote/.chomp! ||
|
81
|
+
%x/git config --get remote.pushDefault/.chomp! ||
|
82
|
+
remote
|
83
|
+
return rm
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def raw_upstream
|
88
|
+
@gitdir.with_dir do
|
89
|
+
up=%x/git rev-parse --abbrev-ref #{@branch.shellescape}@{u}/.chomp!
|
90
|
+
return new_branch(up)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def raw_push
|
95
|
+
@gitdir.with_dir do
|
96
|
+
pu=%x/git rev-parse --abbrev-ref #{@branch.shellescape}@{push}/.chomp!
|
97
|
+
return new_branch(pu)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def raw_hash
|
102
|
+
@hash||=`git rev-parse #{@branch.shellescape}`.chomp!
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
module GitHelpers
|
2
|
+
|
3
|
+
module GitStats
|
4
|
+
#Note: stats-authors give the same result, should be faster, and handle mailcap
|
5
|
+
#inspired by git-mainline//git-rank-contributors
|
6
|
+
def stats_diff(logopts=nil)
|
7
|
+
lines = {}
|
8
|
+
|
9
|
+
with_dir do
|
10
|
+
author = nil
|
11
|
+
state = :pre_author
|
12
|
+
DR::Encoding.fix_utf8(`git log #{DefaultLogOptions} -p #{logopts}`).each_line do |l|
|
13
|
+
case
|
14
|
+
when (state == :pre_author || state == :post_author) && m=l[/Author: (.*)$/,1]
|
15
|
+
#TODO: using directly author=l[]... seems to only affect a block scoped author variable
|
16
|
+
author=m
|
17
|
+
state = :post_author
|
18
|
+
lines[author] ||= {added: 0, deleted: 0, all: 0}
|
19
|
+
when state == :post_author && l =~ /^\+\+\+\s/
|
20
|
+
state = :in_diff
|
21
|
+
when state == :in_diff && l =~ /^[\+\-]/
|
22
|
+
unless l=~ /^(\+\+\+|\-\-\-)\s/
|
23
|
+
lines[author][:all] += 1
|
24
|
+
lines[author][:added] += 1 if l[0]=="+"
|
25
|
+
lines[author][:deleted] += 1 if l[0]=="-"
|
26
|
+
end
|
27
|
+
when state == :in_diff && l =~ /^commit /
|
28
|
+
state = :pre_author
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
lines
|
33
|
+
end
|
34
|
+
|
35
|
+
def output_stats_diff(logopts=nil)
|
36
|
+
lines=stats_diff(logopts)
|
37
|
+
lines.sort_by { |a, c| -c[:all] }.each do |a, c|
|
38
|
+
puts "#{a}: #{c[:all]} lines of diff (+#{c[:added]}/-#{c[:deleted]})"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# inspired by visionmedia//git-line-summary
|
43
|
+
def stats_lines(file)
|
44
|
+
out=""
|
45
|
+
with_dir do
|
46
|
+
out,_suc=SH.run_simple("git", "blame", "--line-porcelain", file, quiet: true)
|
47
|
+
end
|
48
|
+
r={}
|
49
|
+
begin
|
50
|
+
out.each_line do |l|
|
51
|
+
l.match(/^author (.*)/) do |m|
|
52
|
+
r[m[1]]||=0
|
53
|
+
r[m[1]]+=1
|
54
|
+
end
|
55
|
+
end
|
56
|
+
rescue => e
|
57
|
+
warn "Warning: #{e} on #{file}"
|
58
|
+
end
|
59
|
+
r
|
60
|
+
end
|
61
|
+
|
62
|
+
def stats_lines_all
|
63
|
+
r={}
|
64
|
+
all_files.select {|f| SH::Pathname.new(f).text? rescue false}.each do |f|
|
65
|
+
stats_lines(f).each do |k,v|
|
66
|
+
r[k]||=0
|
67
|
+
r[k]+=v
|
68
|
+
end
|
69
|
+
end
|
70
|
+
r
|
71
|
+
end
|
72
|
+
|
73
|
+
def output_stats_lines
|
74
|
+
stats=stats_lines_all
|
75
|
+
total=stats.values.sum
|
76
|
+
stats.sort_by{|k,v| -v}.each do |k,v|
|
77
|
+
puts "- #{k}: #{v} (#{"%2.1f%%" % (100*v/total.to_f)})"
|
78
|
+
end
|
79
|
+
puts "Total lines: #{total}"
|
80
|
+
end
|
81
|
+
|
82
|
+
#Inspired by https://github.com/esc/git-stats/blob/master/git-stats.sh
|
83
|
+
def stats_authors(logopts=nil, more: false)
|
84
|
+
require 'set'
|
85
|
+
#Exemple: --after=..., --before=...,
|
86
|
+
# -w #word diff
|
87
|
+
# -C --find-copies-harder; -M
|
88
|
+
authors={}
|
89
|
+
with_dir do
|
90
|
+
%x/git shortlog -sn #{logopts}/.each_line do |l|
|
91
|
+
commits, author=l.chomp.split(' ', 2)
|
92
|
+
authors[author]={commits: commits.to_i}
|
93
|
+
end
|
94
|
+
|
95
|
+
if more
|
96
|
+
authors.each_key do |a|
|
97
|
+
tot_a=0; tot_r=0; tot_rename=0; files=Set.new
|
98
|
+
%x/git log #{DefaultLogOptions} #{logopts} --numstat --format="%n" --author='#{a}'/.each_line do |l|
|
99
|
+
added, deleted, file=l.chomp.split(' ',3)
|
100
|
+
#puts "#{l} => #{added}, #{deleted}, #{rest}"
|
101
|
+
tot_a+=added.to_i; tot_r+=deleted.to_i
|
102
|
+
next if file.nil?
|
103
|
+
if file.include?(' => ')
|
104
|
+
tot_rename+=1
|
105
|
+
else
|
106
|
+
files.add(file) unless file.empty?
|
107
|
+
end
|
108
|
+
end
|
109
|
+
#rev-list should be faster, but I would need to use
|
110
|
+
# `git rev-parse --revs-only --default HEAD #{logopts.shelljoin}`
|
111
|
+
# to be sure we default to HEAD, and
|
112
|
+
# `git rev-parse --flags #{logopts.shelljoin}` to get the log flags...
|
113
|
+
#tot_merges=%x/git rev-list #{logopts} --merges --author='#{a}'/.each_line.count
|
114
|
+
tot_merges=%x/git log --pretty=oneline #{logopts} --merges --author='#{a}'/.each_line.count
|
115
|
+
authors[a].merge!({added: tot_a, deleted: tot_r, files: files.size, renames: tot_rename, merges: tot_merges})
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
authors
|
120
|
+
end
|
121
|
+
|
122
|
+
def output_stats_authors(logopts=nil)
|
123
|
+
authors=stats_authors(logopts, more: true)
|
124
|
+
authors.each do |a,v|
|
125
|
+
puts "- #{a}: #{v[:commits]} commits (+#{v[:added]}/-#{v[:deleted]}), #{v[:files]} files modified, #{v[:renames]} renames, #{v[:merges]} merges"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
#inspired by visionmedia//git-infos
|
130
|
+
def infos
|
131
|
+
with_dir do
|
132
|
+
puts "## Remote URLs:"
|
133
|
+
puts
|
134
|
+
system("git --no-pager remote -v")
|
135
|
+
puts
|
136
|
+
|
137
|
+
puts "## Remote Branches:"
|
138
|
+
puts
|
139
|
+
system("git --no-pager branch -r")
|
140
|
+
puts
|
141
|
+
|
142
|
+
puts "## Local Branches:"
|
143
|
+
puts
|
144
|
+
system("git --no-pager branch")
|
145
|
+
puts
|
146
|
+
|
147
|
+
puts "## Most Recent Commit:"
|
148
|
+
puts
|
149
|
+
system("git --no-pager log --max-count=1 --pretty=short")
|
150
|
+
puts
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
#inspired by visionmedia//git-summary
|
155
|
+
def summary(logopts=nil)
|
156
|
+
with_dir do
|
157
|
+
project=Pathname.new(%x/git rev-parse --show-toplevel/).basename
|
158
|
+
authors=stats_authors(logopts)
|
159
|
+
commits=authors.map {|a,v| v[:commits]}.sum
|
160
|
+
file_count=%x/git ls-files/.each_line.count
|
161
|
+
active_days=%x/git log --date=short --pretty='format: %ad' #{logopts}/.each_line.uniq.count
|
162
|
+
#This only give the rep age of the current branch; and is not
|
163
|
+
#efficient since we generate the first log
|
164
|
+
#A better way would be to get all the roots commits via
|
165
|
+
# git rev-list --max-parents=0 HEAD
|
166
|
+
#and then look at their ages
|
167
|
+
repository_age=%x/git log --reverse --pretty=oneline --format="%ar" #{logopts}/.each_line.first.sub!('ago','')
|
168
|
+
#total= %x/git rev-list #{logopts}/.each_line.count
|
169
|
+
total=%x/git rev-list --count #{logopts.empty? ? "HEAD" : logopts.shelljoin}/.to_i
|
170
|
+
|
171
|
+
puts " project : #{project}"
|
172
|
+
puts " repo age : #{repository_age}"
|
173
|
+
puts " active : #{active_days} days"
|
174
|
+
puts " commits : #{commits}"
|
175
|
+
puts " files : #{file_count}"
|
176
|
+
puts " authors : #{authors.keys.join(", ")} (Total: #{total})"
|
177
|
+
authors.each do |a,v|
|
178
|
+
puts " - #{a}: #{v[:commits]} (#{"%2.1f" % (100*v[:commits]/commits.to_f)}%)"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,404 @@
|
|
1
|
+
module GitHelpers
|
2
|
+
# status helper
|
3
|
+
module GitStatus
|
4
|
+
|
5
|
+
#get the stash commits
|
6
|
+
def stash
|
7
|
+
if run_success("git rev-parse --verify refs/stash", quiet: true)
|
8
|
+
return run_simple("git rev-list -g refs/stash")
|
9
|
+
else
|
10
|
+
return nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def sequencer(extra_infos=true)
|
15
|
+
read_helper=lambda do |file, ref: false; u|
|
16
|
+
if file.readable?
|
17
|
+
u=file.read.chomp
|
18
|
+
u.sub!(/^refs\/heads\//,"") if ref
|
19
|
+
end
|
20
|
+
u
|
21
|
+
end
|
22
|
+
rb_helper=lambda do |dir; name, onto, rbname, extra|
|
23
|
+
name=read_helper[gitdir+"#{dir}/head-name", ref: true]
|
24
|
+
onto=read_helper[gitdir+"#{dir}/onto", ref: true]
|
25
|
+
onto=branch(onto).name(highlight_detached: "") if onto
|
26
|
+
rbname=""
|
27
|
+
rbname << name if name
|
28
|
+
rbname << "->#{onto}" if onto
|
29
|
+
if dir == "rebase-merge"
|
30
|
+
cur=read_helper[gitdir+"#{dir}/msgnum"]
|
31
|
+
last=read_helper[gitdir+"#{dir}/end"]
|
32
|
+
elsif dir == "rebase-merge"
|
33
|
+
cur=read_helper[gitdir+"#{dir}/next"]
|
34
|
+
last=read_helper[gitdir+"#{dir}/last"]
|
35
|
+
end
|
36
|
+
extra=[]; extra << rbname unless rbname.empty?;
|
37
|
+
extra << "#{cur}/#{last}" if cur and last
|
38
|
+
extra
|
39
|
+
end
|
40
|
+
r=[]; r_extra=[]
|
41
|
+
append=lambda do |seq, extra=""|
|
42
|
+
r << seq
|
43
|
+
if extra_infos
|
44
|
+
extra="" if extra.nil?
|
45
|
+
if extra.is_a?(Array)
|
46
|
+
extra=extra.join(":")
|
47
|
+
end
|
48
|
+
extra = extra.empty? ? "" : "(#{extra})"
|
49
|
+
r_extra << "#{seq}#{extra}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
rb_handler=lambda do |state, mode; extra|
|
53
|
+
if mode == :rbi
|
54
|
+
extra = rb_helper.call("rebase-merge") if extra_infos
|
55
|
+
elsif mode==:am
|
56
|
+
extra = rb_helper.call("rebase-apply") if extra_infos
|
57
|
+
end
|
58
|
+
append.call(state, extra)
|
59
|
+
end
|
60
|
+
|
61
|
+
gitdir=self.gitdir
|
62
|
+
if bare?
|
63
|
+
append.call 'bare'
|
64
|
+
else
|
65
|
+
append.call '.git' if gitdir?
|
66
|
+
end
|
67
|
+
if gitdir.to_s =~ /\/.git\/modules\//
|
68
|
+
append.call 'sub'
|
69
|
+
elsif gitdir.to_s =~ /\/.git\/worktrees\//
|
70
|
+
append.call 'wt'
|
71
|
+
end
|
72
|
+
|
73
|
+
return r unless gitdir
|
74
|
+
if (gitdir+"index.lock").file?
|
75
|
+
append.call "ci" #commit in progress
|
76
|
+
end
|
77
|
+
if (gitdir+"rebase-merge").directory?
|
78
|
+
state=
|
79
|
+
if (gitdir+"rebase-merge/interactive").file?
|
80
|
+
if (gitdir+"rebase-merge/rewritten").exist?
|
81
|
+
"rb-im" #REBASE-im $ rebase -p -i
|
82
|
+
else
|
83
|
+
"rb-i" #REBASE-i
|
84
|
+
end
|
85
|
+
else
|
86
|
+
"rb-m" #REBASE-m $ rebase -p
|
87
|
+
end
|
88
|
+
rb_handler.call(state, :rbi)
|
89
|
+
end
|
90
|
+
if (gitdir+"rebase-apply").directory?
|
91
|
+
state =
|
92
|
+
if (gitdir+"rebase-apply/rebasing").file?
|
93
|
+
"rb" #RB
|
94
|
+
elsif (gitdir+"rebase-apply/applying").file?
|
95
|
+
"am" #AM
|
96
|
+
else
|
97
|
+
"am/rb" #AM/REBASE (should not happen)
|
98
|
+
end
|
99
|
+
rb_handler.call(state, :am)
|
100
|
+
end
|
101
|
+
if (gitdir+"MERGE_HEAD").file?
|
102
|
+
append.call "mg" #MERGING
|
103
|
+
end
|
104
|
+
if (gitdir+"CHERRY_PICK_HEAD").file?
|
105
|
+
state= "ch" #CHERRY-PICKING
|
106
|
+
name=read_helper[gitdir+"CHERRY_PICK_HEAD", ref: true]
|
107
|
+
name=branch(name).name(highlight_detached: "") if name
|
108
|
+
append.call state, name
|
109
|
+
end
|
110
|
+
if (gitdir+"REVERT_HEAD").file?
|
111
|
+
state=rv #REVERTING
|
112
|
+
name=read_helper[gitdir+"REVERT_HEAD", ref: true]
|
113
|
+
name=branch(name).name(highlight_detached: "") if name
|
114
|
+
append.call state, name
|
115
|
+
end
|
116
|
+
if (gitdir+"sequencer").directory?
|
117
|
+
append.call "seq" #when we have a multiple commits cherry-pick or revert
|
118
|
+
# TODO: read the 'todo' file to know if we are picking or reverting?
|
119
|
+
end
|
120
|
+
if (gitdir+"BISECT_LOG").file?
|
121
|
+
state="bi" #BISECTING
|
122
|
+
name=read_helper[gitdir+"BISECT_START", ref: true]
|
123
|
+
append.call state, name
|
124
|
+
end
|
125
|
+
|
126
|
+
if extra_infos == :both
|
127
|
+
return r, r_extra
|
128
|
+
elsif extra_infos
|
129
|
+
r_extra
|
130
|
+
else
|
131
|
+
r
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def status(br='HEAD', ignored: nil, untracked: nil, branch: :full, files: true, sequencer: true, stash: true, detached_name: :detached_infos, **_opts)
|
136
|
+
l_branch={}
|
137
|
+
l_branch=self.branch(br).infos(detached_name: detached_name) if branch == :full
|
138
|
+
r={branch: l_branch}
|
139
|
+
|
140
|
+
if worktree?
|
141
|
+
paths={}
|
142
|
+
l_untracked=[]
|
143
|
+
l_ignored=[]
|
144
|
+
r.merge!({paths: paths, files_untracked: l_untracked, files_ignored: l_ignored})
|
145
|
+
|
146
|
+
staged=0
|
147
|
+
staged_sub=0
|
148
|
+
staged_nonsub=0
|
149
|
+
changed=0
|
150
|
+
changed_nonsub=0
|
151
|
+
changed_sub=0
|
152
|
+
subchanged=0
|
153
|
+
subcommited=0
|
154
|
+
conflicts=0
|
155
|
+
|
156
|
+
complete_infos=lambda do |infos; r|
|
157
|
+
r=[]
|
158
|
+
infos[:xy].each_char do |c|
|
159
|
+
case c
|
160
|
+
when '.'; r << :kept
|
161
|
+
when 'M'; r << :updated
|
162
|
+
when 'A'; r << :added
|
163
|
+
when 'D'; r << :deleted
|
164
|
+
when 'R'; r << :renamed
|
165
|
+
when 'C'; r << :copied
|
166
|
+
when 'U'; r << :unmerged
|
167
|
+
when 'T'; r << :type_change
|
168
|
+
end
|
169
|
+
end
|
170
|
+
infos[:index]=r[0]
|
171
|
+
infos[:worktree]=r[1]
|
172
|
+
|
173
|
+
sub=infos[:sub]
|
174
|
+
if sub[0]=="N"
|
175
|
+
infos[:submodule]=false
|
176
|
+
else
|
177
|
+
infos[:submodule]=true
|
178
|
+
infos[:sub_commited]=sub[1]=="C"
|
179
|
+
infos[:sub_modified]=sub[2]=="M"
|
180
|
+
infos[:sub_untracked]=sub[3]=="U"
|
181
|
+
end
|
182
|
+
|
183
|
+
unless r[0]==:kept or r[0]==:unmerged
|
184
|
+
staged +=1
|
185
|
+
infos[:submodule] ? staged_sub +=1 : staged_nonsub +=1
|
186
|
+
end
|
187
|
+
|
188
|
+
unless r[1]==:kept or r[1]==:unmerged
|
189
|
+
changed +=1
|
190
|
+
if infos[:submodule]
|
191
|
+
changed_sub +=1
|
192
|
+
subchanged +=1 if (infos[:sub_modified]||infos[:sub_untracked])
|
193
|
+
subcommited +=1 if infos[:sub_commited]
|
194
|
+
changed_nonsub +=1 unless (infos[:sub_modified]||infos[:sub_untracked]||infos[:sub_commited]) #for D or T
|
195
|
+
|
196
|
+
else
|
197
|
+
changed_nonsub +=1
|
198
|
+
end
|
199
|
+
end
|
200
|
+
conflicts+=1 if r[0]==:unmerged or r[1]==:unmerged
|
201
|
+
|
202
|
+
if (xscore=infos[:xscore])
|
203
|
+
if xscore[0]=="R"
|
204
|
+
infos[:rename]=true
|
205
|
+
elsif xscore[0]=="C"
|
206
|
+
infos[:copy]=true
|
207
|
+
end
|
208
|
+
infos[:score]=xscore[1..-1].to_i
|
209
|
+
end
|
210
|
+
|
211
|
+
infos
|
212
|
+
end
|
213
|
+
|
214
|
+
if files
|
215
|
+
call=%w(git status --porcelain=v2)
|
216
|
+
status_options=[]
|
217
|
+
status_options << "--branch" if branch and branch != :full
|
218
|
+
status_options << "--untracked-files" if untracked
|
219
|
+
status_options << "--untracked-files=no" if untracked==false
|
220
|
+
status_options << "--ignored" if ignored
|
221
|
+
status_options << "--ignored=no" if ignored==false
|
222
|
+
r[:status_options]=status_options + (branch == :full ? ['--branch'] : [])
|
223
|
+
out=run_simple((call+status_options).shelljoin, error: :quiet, chomp: :lines)
|
224
|
+
out.each do |l|
|
225
|
+
l.match(/# branch.oid\s+(.*)/) do |m|
|
226
|
+
l_branch[:oid]=m[1]
|
227
|
+
end
|
228
|
+
l.match(/# branch.head\s+(.*)/) do |m|
|
229
|
+
br_name=m[1]
|
230
|
+
if br_name=="(detached)" and detached_name
|
231
|
+
l_branch[:detached]=true
|
232
|
+
br_name=self.name_branch(method: detached_name, always: true)
|
233
|
+
else
|
234
|
+
end
|
235
|
+
l_branch[:name]=br_name
|
236
|
+
end
|
237
|
+
l.match(/# branch.upstream\s+(.*)/) do |m|
|
238
|
+
l_branch[:upstream]=m[1]
|
239
|
+
end
|
240
|
+
l.match(/# branch.ab\s+\+(\d*)\s+-(\d*)/) do |m|
|
241
|
+
l_branch[:upstream_ahead]=m[1].to_i
|
242
|
+
l_branch[:upstream_behind]=m[2].to_i
|
243
|
+
end
|
244
|
+
|
245
|
+
l.match(/1 (\S*) (\S*) (\S*) (\S*) (\S*) (\S*) (\S*) (.*)/) do |m|
|
246
|
+
xy=m[1]; sub=m[2]; #modified data, submodule information
|
247
|
+
mH=m[3]; mI=m[4]; mW=m[5]; #file modes
|
248
|
+
hH=m[6]; hI=m[7]; #hash
|
249
|
+
path=m[8]
|
250
|
+
info={xy: xy, sub: sub, mH: mH, mI: mI, mW: mW, hH: hH, hI: hI}
|
251
|
+
paths[path]=complete_infos.call(info)
|
252
|
+
end
|
253
|
+
|
254
|
+
#rename copy
|
255
|
+
l.match(/2 (\S*) (\S*) (\S*) (\S*) (\S*) (\S*) (\S*) (\S*) (.*)\t(.*)/) do |m|
|
256
|
+
xy=m[1]; sub=m[2]; mH=m[3]; mI=m[4]; mW=m[5];
|
257
|
+
hH=m[6]; hI=m[7]; xscore=m[8]
|
258
|
+
path=m[9]; orig_path=m[10]
|
259
|
+
info={xy: xy, sub: sub, mH: mH, mI: mI, mW: mW, hH: hH, hI: hI,
|
260
|
+
xscore: xscore, orig_path: orig_path}
|
261
|
+
paths[path]=complete_infos.call(info)
|
262
|
+
end
|
263
|
+
|
264
|
+
# unmerged
|
265
|
+
l.match(/u (\S*) (\S*) (\S*) (\S*) (\S*) (\S*) (\S*) (\S*) (\S*) (.*)/) do |m|
|
266
|
+
xy=m[1]; sub=m[2]; #modified data, submodule information
|
267
|
+
m1=m[3]; m2=m[4]; m3=m[5]; mW=m[6] #file modes
|
268
|
+
h1=m[7]; h2=m[8]; h3=m[9] #hash
|
269
|
+
path=m[10]
|
270
|
+
info={xy: xy, sub: sub, m1: m1, m2: m2, m3: m3, mW: mW, h1: h1, h2: h2, h3: h3}
|
271
|
+
paths[path]=complete_infos.call(info)
|
272
|
+
end
|
273
|
+
|
274
|
+
l.match(/\? (.*)/) do |m|
|
275
|
+
l_untracked << m[1]
|
276
|
+
end
|
277
|
+
l.match(/! (.*)/) do |m|
|
278
|
+
l_ignored << m[1]
|
279
|
+
end
|
280
|
+
end
|
281
|
+
r[:conflicts]=conflicts
|
282
|
+
r[:staged]=staged
|
283
|
+
r[:staged_nonsub]=staged_nonsub
|
284
|
+
r[:staged_sub]=staged_sub
|
285
|
+
r[:changed]=changed
|
286
|
+
r[:changed_nonsub]=changed_nonsub
|
287
|
+
r[:changed_sub]=changed_sub
|
288
|
+
r[:subchanged]=subchanged
|
289
|
+
r[:subcommited]=subcommited
|
290
|
+
r[:untracked]=l_untracked.length
|
291
|
+
r[:ignored]=l_ignored.length
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
if branch
|
296
|
+
upstream=r.dig(:branch,'upstream')
|
297
|
+
push=r.dig(:branch,'push')
|
298
|
+
if upstream != push
|
299
|
+
r[:push_ahead]=r.dig(:branch,:push_ahead)
|
300
|
+
r[:push_behind]=r.dig(:branch,:push_behind)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
if stash
|
305
|
+
r[:stash]=self.stash&.lines&.length
|
306
|
+
end
|
307
|
+
if sequencer
|
308
|
+
seq, seq_full=self.sequencer(:both)
|
309
|
+
r[:sequencer]=seq
|
310
|
+
r[:full_sequencer]=seq_full
|
311
|
+
end
|
312
|
+
return r
|
313
|
+
end
|
314
|
+
|
315
|
+
#changed_submodule: do we show changed submodule apart?
|
316
|
+
def format_status(br='HEAD', status_infos=nil, changed_submodule: true, max_length: nil, **opts)
|
317
|
+
if status_infos.nil?
|
318
|
+
return "" unless git?
|
319
|
+
status_infos=self.status(br, **opts)
|
320
|
+
end
|
321
|
+
yield status_infos if block_given?
|
322
|
+
branch=status_infos.dig(:branch,:name) || ""
|
323
|
+
ahead=status_infos.dig(:branch,:upstream_ahead)||0
|
324
|
+
behind=status_infos.dig(:branch,:upstream_behind)||0
|
325
|
+
push_ahead=status_infos[:push_ahead]||0
|
326
|
+
push_behind=status_infos[:push_behind]||0
|
327
|
+
# detached=status_infos.dig(:branch,:detached) || false
|
328
|
+
allchanged=status_infos[:changed] ||0
|
329
|
+
if changed_submodule
|
330
|
+
changed=status_infos[:changed_nonsub] ||0
|
331
|
+
subchanged=status_infos[:subchanged] ||0
|
332
|
+
subcommited=status_infos[:subcommited] ||0
|
333
|
+
else
|
334
|
+
changed=status_infos[:changed] ||0
|
335
|
+
end
|
336
|
+
staged=status_infos[:staged] ||0
|
337
|
+
conflicts=status_infos[:conflicts] ||0
|
338
|
+
untracked=status_infos[:untracked] ||0
|
339
|
+
ignored=status_infos[:ignored] || 0
|
340
|
+
stash=status_infos[:stash]||0
|
341
|
+
clean=true
|
342
|
+
clean=false if staged != 0 || allchanged !=0 || untracked !=0 || conflicts !=0 || !worktree? || opts[:files]==false
|
343
|
+
sequencer=status_infos[:sequencer] || []
|
344
|
+
full_sequencer=status_infos[:full_sequencer] || []
|
345
|
+
if stash != 0
|
346
|
+
sequencer << "$#{stash}"
|
347
|
+
full_sequencer << "$#{stash}"
|
348
|
+
end
|
349
|
+
|
350
|
+
# "#{detached ? ":" : ""} # the ':' prefix is done by name now
|
351
|
+
left=
|
352
|
+
"#{branch}".color(:magenta,:bold) <<
|
353
|
+
(ahead==0 ? "" : "↑"<<ahead.to_s ) <<
|
354
|
+
(behind==0 ? "" : "↓"<<behind.to_s ) <<
|
355
|
+
(push_ahead==0 ? "" : "⇡"<<push_ahead.to_s) <<
|
356
|
+
(push_behind==0 ? "" : "⇣"<<push_behind.to_s)
|
357
|
+
|
358
|
+
files=
|
359
|
+
(staged==0 ? "" : "●"+staged.to_s).color(:red) <<
|
360
|
+
(conflicts==0 ? "" : "✖"+conflicts.to_s).color(:red) <<
|
361
|
+
(changed==0 ? "" : "✚"+changed.to_s).color(:blue) <<
|
362
|
+
(subcommited==0 ? "" : ("✦"+subcommited.to_s).color(:blue)) <<
|
363
|
+
(subchanged==0 ? "" : ("✧"+subchanged.to_s).color(:blue)) <<
|
364
|
+
(untracked==0 ? "" : "…" +
|
365
|
+
(opts[:untracked].to_s=="full" ? untracked.to_s : "")
|
366
|
+
).color(:blue) <<
|
367
|
+
(ignored==0 ? "" : "ꜟ" + #❗
|
368
|
+
(opts[:ignored].to_s=="full" ? ignored.to_s : "")
|
369
|
+
).color(:blue) <<
|
370
|
+
(clean ? "✔".color(:green,:bold) : "")
|
371
|
+
|
372
|
+
extra=full_sequencer.join(" ").color(:yellow)
|
373
|
+
|
374
|
+
length=lambda do
|
375
|
+
left.uncolor.size+files.uncolor.size+extra.uncolor.size
|
376
|
+
end
|
377
|
+
shortened=false
|
378
|
+
if max_length
|
379
|
+
if length.call > max_length
|
380
|
+
extra=sequencer.join(" ").color(:yellow)
|
381
|
+
end
|
382
|
+
if length.call > max_length
|
383
|
+
shortened=true unless extra.empty?
|
384
|
+
extra=""
|
385
|
+
end
|
386
|
+
if length.call > max_length
|
387
|
+
shortened=true unless files.empty?
|
388
|
+
files=""
|
389
|
+
end
|
390
|
+
end
|
391
|
+
right=files
|
392
|
+
unless extra.empty?
|
393
|
+
right << " " unless right.empty?
|
394
|
+
right << extra
|
395
|
+
end
|
396
|
+
|
397
|
+
r="(" << left <<
|
398
|
+
(right.empty? ? "" : "|" ) << right <<
|
399
|
+
(shortened ? "⋯" : "") <<
|
400
|
+
")"
|
401
|
+
r
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|