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.
@@ -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