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
data/bin/gitstatus.rb
CHANGED
@@ -1,347 +1,116 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
# Inspired by https://github.com/olivierverdier/zsh-git-prompt
|
3
|
-
# [commit: 350be32093d0585f395413253536d891c247f538,
|
4
|
-
# last commit checked: 0a6c8b610e799040b612db8888945f502a2ddd9d (2016-02-14))
|
5
|
-
#Inspired by the contrib git script
|
6
2
|
|
7
|
-
require "
|
8
|
-
require
|
9
|
-
require "shellwords"
|
10
|
-
require "optparse"
|
11
|
-
require "simplecolor"
|
12
|
-
#require "dr/git" #TODO merge the two implems
|
13
|
-
SimpleColor.mix_in_string
|
3
|
+
require "git_helpers"
|
4
|
+
require 'optparse'
|
14
5
|
|
15
|
-
|
16
|
-
module Run
|
17
|
-
extend(self)
|
18
|
-
#if we get interrupted once, we don't want to launch any more commands
|
19
|
-
@interrupted=false
|
20
|
-
def runstatus(*args)
|
21
|
-
if !@interrupted
|
22
|
-
begin
|
23
|
-
if Open3.respond_to?(:capture3) then
|
24
|
-
out, error, status=Open3.capture3(*args)
|
25
|
-
return out, status.success?
|
26
|
-
else
|
27
|
-
out = `#{args} 2>/dev/null`
|
28
|
-
status=$?
|
29
|
-
return out, status.success?
|
30
|
-
end
|
31
|
-
rescue Interrupt #interruption
|
32
|
-
@interrupted=true
|
33
|
-
return "", false
|
34
|
-
end
|
35
|
-
else
|
36
|
-
return "", false
|
37
|
-
end
|
38
|
-
end
|
39
|
-
def run(*args)
|
40
|
-
msg,_=runstatus(*args)
|
41
|
-
return msg
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
class Git
|
46
|
-
include GitStatus::Run
|
47
|
-
attr_reader :msg
|
48
|
-
|
49
|
-
def git?
|
50
|
-
if @git.nil?
|
51
|
-
_,@git=runstatus "git rev-parse"
|
52
|
-
end
|
53
|
-
return @git
|
54
|
-
end
|
55
|
-
def getgitdir
|
56
|
-
return Pathname.new((run "git rev-parse --git-dir").chomp)
|
57
|
-
end
|
58
|
-
def ingitdir?
|
59
|
-
return (run "git rev-parse --is-inside-git-dir") == "true\n"
|
60
|
-
end
|
61
|
-
def worktree?
|
62
|
-
return (run "git rev-parse --is-inside-work-tree") == "true\n"
|
63
|
-
end
|
64
|
-
def bare?
|
65
|
-
return (run "git rev-parse --is-bare-repository") == "true\n"
|
66
|
-
end
|
67
|
-
|
68
|
-
def cd_and_exec(*args)
|
69
|
-
if @path.nil? then
|
70
|
-
yield(*args)
|
71
|
-
else
|
72
|
-
if File.directory?(@path)
|
73
|
-
Dir.chdir(@path) do
|
74
|
-
yield(*args)
|
75
|
-
end
|
76
|
-
else
|
77
|
-
warn "#{@path} is not a directory"
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def initialize(path=nil)
|
83
|
-
#a nil path means we want information on the current directory
|
84
|
-
if !path.nil?
|
85
|
-
@path=Pathname.new(path).expand_path
|
86
|
-
end
|
87
|
-
cd_and_exec {git?}
|
88
|
-
end
|
89
|
-
|
90
|
-
def get_msg
|
91
|
-
#gitst="git status --porcelain --branch"
|
92
|
-
#too many git are too old to mix --porcelain with --branch
|
93
|
-
gitm=git="git"
|
94
|
-
gitm="#{git} -c color.ui=always" if $opts[:color]
|
95
|
-
gitm="#{gitm} status --short --branch"
|
96
|
-
@msg=run(gitm)
|
97
|
-
end
|
98
|
-
|
99
|
-
def describe_detached_head
|
100
|
-
case $opts[:describe]
|
101
|
-
when "sha1"
|
102
|
-
describe=(run "git rev-parse --short HEAD").chomp
|
103
|
-
when "describe"
|
104
|
-
describe=(run "git describe HEAD").chomp
|
105
|
-
when "contains"
|
106
|
-
describe=(run "git describe --contains HEAD").chomp
|
107
|
-
when "branch"
|
108
|
-
describe=(run "git describe --contains --all HEAD").chomp
|
109
|
-
when "match"
|
110
|
-
describe=(run "git describe --tags --exact-match HEAD").chomp
|
111
|
-
when "all" #try --contains all, then --all
|
112
|
-
describe=(run "git describe --contains --all HEAD").chomp
|
113
|
-
describe=(run "git describe --all HEAD").chomp if describe.nil? or describe.empty?
|
114
|
-
when "magic"
|
115
|
-
describe1=(run "git describe --contains --all HEAD").chomp
|
116
|
-
describe2=(run "git describe --all HEAD").chomp
|
117
|
-
describe= describe1.length < describe2.length ? describe1 : describe2
|
118
|
-
describe=describe1 if describe2.empty?
|
119
|
-
describe=describe2 if describe1.empty?
|
120
|
-
else
|
121
|
-
describe=(run($opts[:describe])).chomp
|
122
|
-
end
|
123
|
-
if describe.empty?
|
124
|
-
describe=(run "git rev-parse --short HEAD").chomp
|
125
|
-
end
|
126
|
-
@branch=":#{describe}"
|
127
|
-
end
|
128
|
-
|
129
|
-
def parse_head(head)
|
130
|
-
@ahead=@behind=0
|
131
|
-
if (head =~ /## Initial commit on (\S*)/) then
|
132
|
-
@branch=$1
|
133
|
-
if @branch =~ /(\S*)\.\.\./
|
134
|
-
@branch=$1
|
135
|
-
end
|
136
|
-
@branch+="…"
|
137
|
-
elsif (head =~ /## (\S*) \(no branch\)/) then
|
138
|
-
describe_detached_head
|
139
|
-
elsif (head =~ /## (\S*)(.*)/) then
|
140
|
-
branchs=$1
|
141
|
-
rest=$2
|
142
|
-
if (branchs =~ /(\S*)\.\.\.(\S*)/) then
|
143
|
-
@branch=$1
|
144
|
-
remote=$2
|
145
|
-
else
|
146
|
-
@branch=branchs
|
147
|
-
end
|
148
|
-
if (rest =~ /.*\[ahead\s+(\d*)(.*)/) then
|
149
|
-
@ahead=$1.to_i
|
150
|
-
rest=$2
|
151
|
-
end
|
152
|
-
if (rest =~ /.*behind\s+(\d*)\]/) then
|
153
|
-
@behind=$1.to_i
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
def parse_msg
|
159
|
-
msg=@msg
|
160
|
-
#find the branch name, and if we are behind/ahead of upstream
|
161
|
-
lines=msg.uncolor.lines.to_a
|
162
|
-
return if lines.empty?
|
163
|
-
head=lines.shift
|
164
|
-
parse_head(head)
|
165
|
-
|
166
|
-
#get status of files
|
167
|
-
@changed=@staged=@untracked=@conflicts=0
|
168
|
-
lines.each do |line|
|
169
|
-
index = line[0];
|
170
|
-
workdir = line[1];
|
171
|
-
#puts "index: #{index}, workdir: #{workdir}"
|
172
|
-
if index=~/[DRAMTC]/ then
|
173
|
-
@staged+=1
|
174
|
-
end
|
175
|
-
if workdir=~ /[DMT]/ then
|
176
|
-
@changed+=1
|
177
|
-
end
|
178
|
-
if workdir=='?' || index=='?' then
|
179
|
-
@untracked+=1
|
180
|
-
end
|
181
|
-
if workdir=='U' || index=='U' then
|
182
|
-
@conflicts+=1
|
183
|
-
end
|
184
|
-
end
|
185
|
-
@clean=true
|
186
|
-
@clean=false if @staged != 0 || @changed !=0 ||
|
187
|
-
@untracked !=0 || @conflicts !=0
|
188
|
-
end
|
189
|
-
|
190
|
-
def get_status
|
191
|
-
if worktree?
|
192
|
-
get_msg
|
193
|
-
parse_msg
|
194
|
-
if $opts[:sequencer] and !@msg.empty?
|
195
|
-
@sequencer=""
|
196
|
-
gitdir=getgitdir
|
197
|
-
if (gitdir+"rebase-merge").directory?
|
198
|
-
if (gitdir+"rebase-merge/interactive").file?
|
199
|
-
@sequencer<<" rb-i " #REBASE-i
|
200
|
-
else
|
201
|
-
@sequencer<<" rb-m " #REBASE-m
|
202
|
-
end
|
203
|
-
@sequencer<<(gitdir+"rebase-merge/head-name").read.chomp.sub(/^refs\/heads\//,"")
|
204
|
-
end
|
205
|
-
if (gitdir+"rebase-apply").directory?
|
206
|
-
if (gitdir+"rebase-apply/rebasing").file?
|
207
|
-
@sequencer<<" rb" #RB
|
208
|
-
elsif (gitdir+"rebase-apply/applying").file?
|
209
|
-
@sequencer<<" am" #AM
|
210
|
-
else
|
211
|
-
@sequencer<<" am/rb" #AM/REBASE
|
212
|
-
end
|
213
|
-
end
|
214
|
-
if (gitdir+"MERGE_HEAD").file?
|
215
|
-
@sequencer<<" mg" #MERGING
|
216
|
-
end
|
217
|
-
if (gitdir+"CHERRY_PICK_HEAD").file?
|
218
|
-
@sequencer<<" ch" #CHERRY-PICKING
|
219
|
-
end
|
220
|
-
if (gitdir+"BISECT_LOG").file?
|
221
|
-
@sequencer<<" bi" #BISECTING
|
222
|
-
end
|
223
|
-
_,stashstatus=runstatus "git rev-parse --verify refs/stash"
|
224
|
-
if stashstatus
|
225
|
-
stashs=run "git rev-list -g refs/stash"
|
226
|
-
@sequencer<<" $#{stashs.lines.to_a.length}" #Stash
|
227
|
-
end
|
228
|
-
end
|
229
|
-
return !@msg.empty?
|
230
|
-
else
|
231
|
-
if $opts[:sequencer]
|
232
|
-
if ingitdir?
|
233
|
-
if bare?
|
234
|
-
@branch="|bare|"
|
235
|
-
else
|
236
|
-
@branch="|.git|"
|
237
|
-
end
|
238
|
-
end
|
239
|
-
end
|
240
|
-
end
|
241
|
-
return false
|
242
|
-
end
|
243
|
-
|
244
|
-
def status
|
245
|
-
cd_and_exec { get_status } if git?
|
246
|
-
end
|
247
|
-
|
248
|
-
def prompt
|
249
|
-
if status
|
250
|
-
return "(" <<
|
251
|
-
@branch.color(:magenta,:bold) <<
|
252
|
-
(@ahead==0 ? "" : "↑"<<@ahead.to_s ) <<
|
253
|
-
(@behind==0 ? "" : "↓"<<@behind.to_s ) <<
|
254
|
-
"|" <<
|
255
|
-
(@staged==0 ? "" : ("●"+@staged.to_s).color(:red) ) <<
|
256
|
-
(@conflicts==0 ? "" : ("✖"+@conflicts.to_s).color(:red) ) <<
|
257
|
-
(@changed==0 ? "" : ("✚"+@changed.to_s).color(:blue) ) <<
|
258
|
-
(@untracked==0 ? "" : "…" ) <<
|
259
|
-
(@clean ? "✔".color(:green,:bold) : "" ) <<
|
260
|
-
(@sequencer.empty? ? "" : @sequencer.color(:yellow) ) <<
|
261
|
-
")"
|
262
|
-
else
|
263
|
-
return "(" << @branch.color(:magenta,:bold) << ")" if @branch
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
def porcelain
|
268
|
-
if git?
|
269
|
-
return "#{@branch}\n#{@ahead}\n#{@behind}\n#{@staged}\n#{@conflicts}\n#{@changed}\n#{@untracked}\n#{@clean?1:0}\n#{@sequencer}\n"
|
270
|
-
else
|
271
|
-
return ""
|
272
|
-
end
|
273
|
-
end
|
274
|
-
end
|
275
|
-
end
|
276
|
-
|
277
|
-
$opts={:color => true, :indent => nil, :sequencer => true, :describe => "magic"}
|
6
|
+
opts={:color => true}
|
278
7
|
optparse = OptionParser.new do |opt|
|
279
8
|
opt.banner= "#{File.basename($0)} [options] git_dirs"
|
280
9
|
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|
|
281
|
-
|
282
|
-
|
283
|
-
opt.on("--[no-]porcelain", "Don't format the status but output it in a machine convenient format") do |v|
|
284
|
-
$opts[:porcelain]=v
|
10
|
+
opts[:prompt]=v
|
11
|
+
opts[:max_length]=40 if v
|
285
12
|
end
|
286
|
-
opt.on("-s", "--[no-]status", "List file", "Print the output of git status additionally of what this program parse") do |v|
|
287
|
-
|
13
|
+
opt.on("-s", "--[no-]status[=options]", "List file", "Print the output of git status additionally of what this program parse") do |v|
|
14
|
+
opts[:status]=v
|
288
15
|
end
|
289
16
|
opt.on("-c", "--[no-]color", "Color output", "on by default") do |v|
|
290
|
-
|
17
|
+
opts[:color]=v
|
291
18
|
end
|
292
19
|
opt.on("--[no-]sequencer", "Show sequencer data (and also look for bare directory)", "on by default") do |v|
|
293
|
-
|
20
|
+
opts[:sequencer]=v
|
294
21
|
end
|
295
22
|
opt.on("--indent spaces", Integer, "Indent to use if showing git status", "2 by default, 0 for empty ARGV") do |v|
|
296
|
-
|
23
|
+
opts[:indent]=v
|
24
|
+
end
|
25
|
+
opt.on("--describe sha1/describe/contains/branch/match/all/magic", "How to describe a detached HEAD", "'branch-fb' by default") do |v|
|
26
|
+
opts[:detached_name]=v
|
27
|
+
end
|
28
|
+
opt.on("--[no-]ignored[=full]", "-i", "Show ignored files") do |v|
|
29
|
+
opts[:ignored]=v
|
30
|
+
end
|
31
|
+
opt.on("--[no-]untracked[=full]", "-u", "Show untracked files") do |v|
|
32
|
+
opts[:untracked]=v
|
33
|
+
end
|
34
|
+
opt.on("--[no-]branch", "Get branch infos (true by default)") do |v|
|
35
|
+
opts[:branch]=v
|
36
|
+
end
|
37
|
+
opt.on("--[no-]files", "Get files infos (true by default)") do |v|
|
38
|
+
opts[:files]=v
|
297
39
|
end
|
298
|
-
opt.on("--
|
299
|
-
|
40
|
+
opt.on("--use=branch_name", "Show a different branch than HEAD") do |v|
|
41
|
+
opts[:use]=v
|
42
|
+
end
|
43
|
+
opt.on("--[no-]raw", "Show raw status infos") do |v|
|
44
|
+
opts[:raw]=v
|
300
45
|
end
|
301
46
|
opt.on("--sm", "Recurse on each submodules") do |v|
|
302
|
-
|
47
|
+
opts[:submodules]=v
|
48
|
+
end
|
49
|
+
opt.on("--max-length=length", "Maximum status length", Integer) do |v|
|
50
|
+
opts[:max_length]=v
|
51
|
+
end
|
52
|
+
opt.on("--[no-]debug", "Debug git calls") do |v|
|
53
|
+
opts[:debug]=v
|
303
54
|
end
|
304
55
|
end
|
305
56
|
optparse.parse!
|
306
57
|
|
307
|
-
if
|
58
|
+
if !opts[:color]
|
308
59
|
SimpleColor.enabled=false
|
309
60
|
end
|
61
|
+
if opts[:debug]
|
62
|
+
SH.debug
|
63
|
+
end
|
310
64
|
|
311
65
|
def prettify_dir(dir)
|
312
|
-
return dir.
|
66
|
+
return '' if dir.nil?
|
67
|
+
return (dir.sub(/^#{ENV['HOME']}/,"~"))+": "
|
313
68
|
end
|
314
69
|
|
315
|
-
def gs_output(dir)
|
316
|
-
g=
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
70
|
+
def gs_output(dir=".", **opts)
|
71
|
+
g=GitHelpers::GitDir.new(dir || ".")
|
72
|
+
status={}
|
73
|
+
arg=opts.delete(:use)
|
74
|
+
args= arg.nil? ? [] : [arg]
|
75
|
+
if opts[:raw]
|
76
|
+
puts "#{prettify_dir(dir)}#{g.status(*args,**opts)}"
|
77
|
+
else
|
78
|
+
puts "#{prettify_dir(dir)}#{g.format_status(*args,**opts) {|s| status=s}}"
|
79
|
+
end
|
80
|
+
if opts[:status] and g.worktree?
|
81
|
+
g.with_dir do
|
82
|
+
options=opts[:status]
|
83
|
+
if options.is_a?(String)
|
84
|
+
options=options.split(',')
|
85
|
+
else
|
86
|
+
options=[]
|
87
|
+
end
|
88
|
+
out=SH.run_simple("git #{opts[:color] ? "-c color.ui=always" : ""} status --short #{(status[:status_options]+options).shelljoin}")
|
89
|
+
out.each_line.each do |line|
|
90
|
+
print " "*(opts[:indent]||0) + line
|
91
|
+
end
|
321
92
|
end
|
322
93
|
end
|
323
94
|
end
|
324
95
|
|
325
|
-
if
|
326
|
-
puts GitStatus::Git.new.porcelain
|
327
|
-
elsif $opts[:prompt]
|
96
|
+
if opts[:prompt]
|
328
97
|
SimpleColor.enabled=:shell
|
329
|
-
prompt=
|
98
|
+
prompt=GitHelpers.create.format_status(**opts)
|
330
99
|
puts prompt if prompt #in ruby1.8, puts nil output nil...
|
331
100
|
else
|
332
101
|
args=ARGV
|
333
102
|
if args.empty?
|
334
|
-
|
103
|
+
opts[:indent]=0 unless opts[:indent]
|
335
104
|
args=[nil]
|
336
105
|
else
|
337
|
-
|
106
|
+
opts[:indent]=2 unless opts[:indent]
|
338
107
|
end
|
339
108
|
args.each do |dir|
|
340
|
-
gs_output(dir)
|
341
|
-
if
|
342
|
-
|
343
|
-
%x/git submodule status/.each_line.map { |l| l.split[1] }.each do |
|
344
|
-
gs_output(
|
109
|
+
gs_output(dir,**opts)
|
110
|
+
if opts[:submodules]
|
111
|
+
g.with_dir do
|
112
|
+
%x/git submodule status/.each_line.map { |l| l.split[1] }.each do |sdir|
|
113
|
+
gs_output(sdir, **opts)
|
345
114
|
end
|
346
115
|
end
|
347
116
|
end
|
data/lib/git_helpers.rb
CHANGED
@@ -1,344 +1,38 @@
|
|
1
1
|
require 'git_helpers/version'
|
2
|
-
require '
|
2
|
+
require 'simplecolor/mixin'
|
3
|
+
require 'shell_helpers'
|
3
4
|
require 'dr/base/bool'
|
4
|
-
require '
|
5
|
+
require 'git_helpers/git_dir'
|
6
|
+
require 'git_helpers/branch'
|
5
7
|
|
8
|
+
#git functions helper
|
9
|
+
#small library wrapping git; use rugged for more interesting things
|
6
10
|
module GitHelpers
|
7
|
-
|
8
|
-
|
9
|
-
#
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
|
-
|
21
|
-
#we could also use 'git -C #{@dir}' for each git invocation
|
22
|
-
def with_dir
|
23
|
-
Dir.chdir(@dir) { yield }
|
24
|
-
end
|
25
|
-
|
26
|
-
def all_files
|
27
|
-
with_dir do
|
28
|
-
%x/git ls-files -z/.split("\0")
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
#are we in a git folder?
|
33
|
-
def git?(quiet: false)
|
34
|
-
launch="git rev-parse"
|
35
|
-
launch=launch + " 2>/dev/null" if quiet
|
36
|
-
with_dir do
|
37
|
-
system launch
|
38
|
-
return Bool.to_bool($?)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
#are we in .git/?
|
43
|
-
def gitdir?
|
44
|
-
with_dir do
|
45
|
-
return Bool.to_bool(%x/git rev-parse --is-inside-git-dir/)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
#are we in the worktree?
|
49
|
-
def worktree?
|
50
|
-
with_dir do
|
51
|
-
return Bool.to_bool(%x/git rev-parse --is-inside-work-tree/)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
#are we in a bare repo?
|
55
|
-
def bare?
|
56
|
-
with_dir do
|
57
|
-
return Bool.to_bool(%x/git rev-parse --is-bare-repository/)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
#return the absolute path of the toplevel
|
62
|
-
def toplevel
|
63
|
-
with_dir do
|
64
|
-
return Pathname.new(%x/git rev-parse --show-toplevel/.chomp)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
#relative path from toplevel to @dir
|
68
|
-
def prefix
|
69
|
-
with_dir do
|
70
|
-
return Pathname.new(%x/git rev-parse --show-prefix/.chomp)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
#return the relative path from @dir to the toplevel
|
74
|
-
def relative_toplevel
|
75
|
-
with_dir do
|
76
|
-
return Pathname.new(%x/git rev-parse --show-cdup/.chomp)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
#get path to .git directory (can be relative or absolute)
|
80
|
-
def gitdir
|
81
|
-
with_dir do
|
82
|
-
return Pathname.new(%x/git rev-parse --git-dir/.chomp)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
def with_toplevel(&b)
|
87
|
-
with_dir do
|
88
|
-
dir=relative_toplevel
|
89
|
-
if !dir.to_s.empty?
|
90
|
-
Dir.chdir(dir,&b)
|
91
|
-
else
|
92
|
-
warn "No toplevel found, executing inside dir #{@dir}"
|
93
|
-
with_dir(&b)
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
#return a list of submodules
|
99
|
-
def submodules
|
100
|
-
with_dir do
|
101
|
-
return %x/git submodule status/.each_line.map { |l| l.split[1] }
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
def get_config(*args)
|
106
|
-
with_dir do
|
107
|
-
return %x/git config #{args.shelljoin}/.chomp
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
def current_branch(always: true)
|
112
|
-
with_dir do
|
113
|
-
branchname= %x/git symbolic-ref -q --short HEAD/.chomp!
|
114
|
-
branchname||= %x/git rev-parse --verify HEAD/.chomp! if always
|
115
|
-
return branch(branchname)
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
def head
|
120
|
-
return branch('HEAD')
|
121
|
-
end
|
122
|
-
|
123
|
-
#return all branches that have an upstream
|
124
|
-
#if branches=:all look through all branches
|
125
|
-
def all_upstream_branches(branches)
|
126
|
-
#TODO
|
127
|
-
upstreams=%x!git for-each-ref --format='%(upstream:short)' refs/heads/branch/!
|
128
|
-
end
|
129
|
-
|
130
|
-
def get_topic_branches(*branches, complete: :local)
|
131
|
-
if branches.length >= 2
|
132
|
-
return branch(branches[0]), branch(branches[1])
|
133
|
-
elsif branches.length == 1
|
134
|
-
b=branch(branches[0])
|
135
|
-
if complete == :local
|
136
|
-
return current_branch, b
|
137
|
-
elsif complete == :remote
|
138
|
-
return b, b.upstream
|
139
|
-
else
|
140
|
-
fail "complete keyword should be :local or :remote"
|
141
|
-
end
|
142
|
-
else
|
143
|
-
c=current_branch
|
144
|
-
return c, c.upstream
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
def branch(branch="HEAD")
|
149
|
-
GitBranch.new(branch, dir: @self)
|
150
|
-
end
|
151
|
-
|
152
|
-
def branch_infos(*branches, local: false, remote: false, tags: false)
|
153
|
-
query=branches.map {|b| name_branch(b, method: 'full_name')}
|
154
|
-
query << 'refs/heads' if local
|
155
|
-
query << 'refs/remotes' if remote
|
156
|
-
query << 'refs/tags' if tags
|
157
|
-
r={}
|
158
|
-
format=%w(refname refname:short objecttype objectsize objectname upstream upstream:short upstream:track upstream:remotename upstream:remoteref push push:short push:remotename push:remoteref HEAD symref)
|
159
|
-
out=SH::Run.run_simple("git for-each-ref --format '#{format.map {|f| "%(#{f})"}.join(',')}, ' #{query.shelljoin}", chomp: :lines)
|
160
|
-
out.each do |l|
|
161
|
-
infos=l.split(',')
|
162
|
-
full_name=infos[0]
|
163
|
-
r[full_name]=Hash[format.zip(infos)]
|
164
|
-
type=if full_name.start_with?("refs/heads/")
|
165
|
-
:local
|
166
|
-
elsif full_name.start_with?("refs/remotes/")
|
167
|
-
:remote
|
168
|
-
elsif full_name.start_with?("refs/tags/")
|
169
|
-
:tags
|
170
|
-
end
|
171
|
-
name = case type
|
172
|
-
when :local
|
173
|
-
full_name.delete_prefix("refs/heads/")
|
174
|
-
when :remote
|
175
|
-
full_name.delete_prefix("refs/remotes/")
|
176
|
-
when :tags
|
177
|
-
full_name.delete_prefix("refs/tags/")
|
178
|
-
end
|
179
|
-
r[full_name][:type]=type
|
180
|
-
r[full_name][:name]=name
|
181
|
-
end
|
182
|
-
r
|
183
|
-
end
|
184
|
-
|
185
|
-
def name_branch(branch,*args)
|
186
|
-
self.branch(branch).name(*args)
|
187
|
-
end
|
188
|
-
end
|
11
|
+
DefaultLogOptions=["-M", "-C", "--no-color"].shelljoin
|
12
|
+
# we only call git to get status updates, we never modify the git dir
|
13
|
+
# so locks are not required, pass that information through the env
|
14
|
+
# variabole:
|
15
|
+
ENV['GIT_OPTIONAL_LOCKS']="0"
|
16
|
+
# another solution would be to invoke git via git --no-optional-locks
|
17
|
+
# each time. For now the env variable is easier to use.
|
18
|
+
# Note that the only optional lock is for git status currently.
|
19
|
+
# There is the following trade-off: If git-status will not take locks, it
|
20
|
+
# cannot update the index to save refresh information and reuse the next
|
21
|
+
# time. So do we want to use this?
|
189
22
|
|
190
23
|
extend self
|
191
24
|
add_instance_methods = lambda do |klass|
|
192
25
|
klass.instance_methods(false).each do |m|
|
193
|
-
define_method(m) do |*args,&b|
|
194
|
-
GitDir.new.public_send(m,*args,&b)
|
26
|
+
define_method(m) do |*args,**kws,&b|
|
27
|
+
GitDir.new.public_send(m,*args,**kws,&b)
|
195
28
|
end
|
196
29
|
end
|
197
30
|
end
|
198
|
-
|
199
|
-
|
200
|
-
add_instance_methods.call(GitExtraInfos)
|
201
|
-
|
202
|
-
class GitBranch
|
203
|
-
attr_accessor :gitdir
|
204
|
-
attr_accessor :branch
|
205
|
-
attr_writer :infos
|
206
|
-
|
207
|
-
def initialize(branch="HEAD", dir: ".")
|
208
|
-
@gitdir=dir.is_a?(GitDir) ? dir : GitDir.new(dir)
|
209
|
-
@branch=branch
|
210
|
-
end
|
211
|
-
|
212
|
-
def new_branch(name)
|
213
|
-
self.class.new(name, @gitdir)
|
214
|
-
end
|
215
|
-
|
216
|
-
def to_s
|
217
|
-
@branch.to_s
|
218
|
-
end
|
219
|
-
|
220
|
-
def nil?
|
221
|
-
@branch.nil?
|
222
|
-
end
|
223
|
-
|
224
|
-
def shellescape
|
225
|
-
@branch.shellescape
|
226
|
-
end
|
227
|
-
|
228
|
-
def infos
|
229
|
-
return @infos if @infos
|
230
|
-
infos=branch_infos
|
231
|
-
type=infos[:type]
|
232
|
-
if type == :local
|
233
|
-
rebase=gitdir.get_config("branch.#{name}.rebase")
|
234
|
-
rebase = false if rebase.empty?
|
235
|
-
rebase = true if rebase == "true"
|
236
|
-
infos[:rebase]=rebase
|
237
|
-
end
|
238
|
-
@infos=infos
|
239
|
-
end
|
240
|
-
|
241
|
-
def name(method: "name", always: true)
|
242
|
-
@gitdir.with_dir do
|
243
|
-
case method
|
244
|
-
when "sha1"
|
245
|
-
describe=%x"git rev-parse --short #{@branch.shellescape}".chomp!
|
246
|
-
when "describe"
|
247
|
-
describe=%x"git describe #{@branch.shellescape}".chomp!
|
248
|
-
when "contains"
|
249
|
-
describe=%x"git describe --contains #{@branch.shellescape}".chomp!
|
250
|
-
when "match"
|
251
|
-
describe=%x"git describe --tags --exact-match #{@branch.shellescape}".chomp!
|
252
|
-
when "topic"
|
253
|
-
describe=%x"git describe --all #{@branch.shellescape}".chomp!
|
254
|
-
when "branch"
|
255
|
-
describe=%x"git describe --contains --all #{@branch.shellescape}".chomp!
|
256
|
-
when "topic-fb" #try --all, then --contains all
|
257
|
-
describe=%x"git describe --all #{@branch.shellescape}".chomp!
|
258
|
-
describe=%x"git describe --contains --all #{@branch.shellescape}".chomp! if describe.nil? or describe.empty?
|
259
|
-
when "branch-fb" #try --contains all, then --all
|
260
|
-
describe=%x"git describe --contains --all #{@branch.shellescape}".chomp!
|
261
|
-
describe=%x"git describe --all #{@branch.shellescape}".chomp! if describe.nil? or describe.empty?
|
262
|
-
when "magic"
|
263
|
-
describe1=%x"git describe --contains --all #{@branch.shellescape}".chomp!
|
264
|
-
describe2=%x"git describe --all #{@branch.shellescape}".chomp!
|
265
|
-
describe= describe1.length < describe2.length ? describe1 : describe2
|
266
|
-
describe=describe1 if describe2.empty?
|
267
|
-
describe=describe2 if describe1.empty?
|
268
|
-
when "name"
|
269
|
-
describe=%x"git rev-parse --abbrev-ref --symbolic-full-name #{@branch.shellescape}".chomp!
|
270
|
-
when "full_name"
|
271
|
-
describe=%x"git rev-parse --symbolic-full-name #{@branch.shellescape}".chomp!
|
272
|
-
when "symbolic"
|
273
|
-
describe=%x"git rev-parse --symbolic #{@branch.shellescape}".chomp!
|
274
|
-
else
|
275
|
-
describe=%x/#{method}/.chomp! unless method.nil? or method.empty?
|
276
|
-
end
|
277
|
-
if (describe.nil? or describe.empty?) and always
|
278
|
-
describe=%x/git rev-parse --short #{@branch.shellescape}/.chomp!
|
279
|
-
end
|
280
|
-
return describe
|
281
|
-
end
|
282
|
-
end
|
283
|
-
|
284
|
-
def rebase?
|
285
|
-
@gitdir.with_dir do
|
286
|
-
rb=%x/git config --bool branch.#{@branch.shellescape}.rebase/.chomp!
|
287
|
-
rb||=%x/git config --bool pull.rebase/.chomp!
|
288
|
-
return rb=="true"
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
|
-
def remote
|
293
|
-
@gitdir.with_dir do
|
294
|
-
rm=%x/git config --get branch.#{@branch.shellescape}.remote/.chomp!
|
295
|
-
rm||="origin"
|
296
|
-
return rm
|
297
|
-
end
|
298
|
-
end
|
299
|
-
|
300
|
-
def push_remote
|
301
|
-
@gitdir.with_dir do
|
302
|
-
rm= %x/git config --get branch.#{@branch.shellescape}.pushRemote/.chomp! ||
|
303
|
-
%x/git config --get remote.pushDefault/.chomp! ||
|
304
|
-
remote
|
305
|
-
return rm
|
306
|
-
end
|
307
|
-
end
|
308
|
-
|
309
|
-
def upstream
|
310
|
-
@gitdir.with_dir do
|
311
|
-
up=%x/git rev-parse --abbrev-ref #{@branch.shellescape}@{u}/.chomp!
|
312
|
-
return new_branch(up)
|
313
|
-
end
|
314
|
-
end
|
315
|
-
|
316
|
-
def push
|
317
|
-
@gitdir.with_dir do
|
318
|
-
pu=%x/git rev-parse --abbrev-ref #{@branch.shellescape}@{push}/.chomp!
|
319
|
-
return new_branch(pu)
|
320
|
-
end
|
321
|
-
end
|
322
|
-
|
323
|
-
def hash
|
324
|
-
@hash||=`git rev-parse #{@branch.shellescape}`.chomp!
|
325
|
-
end
|
326
|
-
|
327
|
-
def ==(other)
|
328
|
-
@branch == other.branch && @gitdir=other.gitdir
|
329
|
-
end
|
330
|
-
|
331
|
-
#return upstream + push if push !=upstream
|
332
|
-
def related
|
333
|
-
up=upstream
|
334
|
-
pu=push
|
335
|
-
pu=new_branch(nil) if up==pu
|
336
|
-
return up, pu
|
337
|
-
end
|
338
|
-
|
339
|
-
def branch_infos
|
340
|
-
@gitdir.branch_infos(@branch).values.first
|
341
|
-
end
|
31
|
+
GitDir.ancestors.each do |mod|
|
32
|
+
add_instance_methods.call(mod) if mod.to_s =~ /^GitHelpers::/
|
342
33
|
end
|
343
34
|
|
35
|
+
def self.create(dir='.')
|
36
|
+
GitDir.new(dir)
|
37
|
+
end
|
344
38
|
end
|