git_helpers 0.1.0 → 0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ChangeLog.md +99 -2
- data/LICENSE.txt +1 -1
- data/Rakefile +7 -10
- data/bin/diff-fancy.rb +1 -0
- data/bin/gitstatus.old.rb +349 -0
- data/bin/gitstatus.rb +74 -305
- data/lib/git_helpers.rb +24 -330
- data/lib/git_helpers/branch.rb +216 -0
- data/lib/git_helpers/branch_infos.rb +178 -0
- data/lib/git_helpers/diff.rb +701 -0
- data/lib/git_helpers/extra_helpers.rb +24 -220
- data/lib/git_helpers/git_dir.rb +176 -0
- data/lib/git_helpers/raw_helpers.rb +105 -0
- data/lib/git_helpers/stats.rb +183 -0
- data/lib/git_helpers/status.rb +404 -0
- data/lib/git_helpers/submodules.rb +32 -0
- data/lib/git_helpers/version.rb +1 -1
- metadata +13 -3
- data/bin/diff-fancy.rb +0 -699
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
|