git_helpers 0.1.0 → 0.2

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