git_helpers 0.1.0 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|