This repository has been archived by the owner on Dec 21, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
/
git-wtf
executable file
·195 lines (165 loc) · 5.31 KB
/
git-wtf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
#!/usr/bin/env ruby
$debug = false
require 'yaml'
CONFIG_FILENAME = ".git-wtf"
begin
require 'rubygems'
require 'term/ansicolor'
HAS_COLOR=true
include Term::ANSIColor
rescue LoadError
HAS_COLOR=false
end
class Numeric; def pluralize s; "#{to_s} #{s}" + (self != 1 ? "s" : "") end end
class Hash
def leaves
children = values.select { |v| v.is_a?(Hash) }
children.length > 0 ? children.map { |c| c.leaves }.flatten : self
end
end
def die s
$stderr.puts "Error: #{s}"
exit(-1)
end
COLOR_ATTRIBUTES = {
0 => black,
1 => red,
2 => green,
3 => yellow,
4 => blue,
5 => magenta,
6 => cyan,
7 => dark,
8 => bold,
9 => reset
} if HAS_COLOR
def cputs(string='')
if HAS_COLOR
COLOR_ATTRIBUTES.each { |num, color| string.gsub!("&|#{num}", color) }
else
string.gsub!(/&\|\d/, '')
end
puts string
end
def run_command(command)
output = IO.popen(command, 'r').read
if $debug
cputs "-- begin run_command --"
cputs "executing: #{command}"
cputs "output:"
cputs output
cputs "-- end run_command --"
end
output
end
def commits_between from, to
commits = run_command(%{ git log --pretty=format:"%h-%ae-%s" #{from}..#{to} }).split(/[\r\n]+/).map do |raw_commit|
raw_commit_parts = raw_commit.split('-')
{
:hash => raw_commit_parts.shift,
:author => raw_commit_parts.shift.split('@').first,
:message => raw_commit_parts.join('-')
}
end
max_author_size = commits.map { |c| c[:author].length }.sort.last
commits.map do |commit|
"[&|2%s&|9] &|8&|4%#{max_author_size}s&|9 %s" % [commit[:hash], commit[:author], commit[:message]]
end
end
begin
$config = YAML::load_file(CONFIG_FILENAME)
raise 'invalid configuration format' unless $config["version_branches"].length > 0
rescue
# cputs <<-CONFIG.gsub(/^ /, '')
# Create a .git-wtf in your git repository directory that looks like:
# --
# version_branches:
# - stable-2.0
# CONFIG
# exit 1
$config = { 'version_branches' => [] }
end
current_branch = File.read(File.join('.git', 'HEAD')).chomp.split('/').last
$branches = Dir[File.join('.git', 'refs', 'heads', '*')].inject({}) do |hash, ref|
name = File.basename(ref)
rev = File.read(ref).chomp
current = (name == current_branch)
version = $config["version_branches"].include?(name)
remote = run_command("git config --get branch.#{name}.remote").chomp
merge = run_command("git config --get branch.#{name}.merge").chomp.split('/').last
hash.update({ name => {
:name => name,
:rev => rev,
:current => current,
:version => version,
:merge => { :remote => remote, :branch => merge }
}})
end
$remotes = Dir[File.join('.git', 'refs', 'remotes', '*', '*')].inject({}) do |hash, ref|
ref_parts = ref.split('/')
name = ref_parts.pop
remote = ref_parts.pop
rev = File.read(ref).chomp
hash[remote] ||= {}
hash[remote][name] = { :name => name, :remote => remote, :rev => rev }
hash
end
$branches.each do |name, branch|
$branches[name][:parent] = begin
merge = branch[:merge]
remote = merge[:remote] == '.' ? $branches : $remotes[merge[:remote]]
remote ? remote[merge[:branch]] : nil
end
end
repo = {
:master => ($branches.values + $remotes['origin'].values).detect { |b| b[:name] == 'master' },
:current => $branches.values.detect { |b| b[:current] },
:versions => $branches.values.select { |b| b[:version] },
:features => $branches.values.select { |b| !(b[:version] || b[:name] == 'master') },
:remotes => $remotes
}
def branch_sync_status(local, remote, show_outgoing=true, show_incoming=true, good="in sync", bad="out of sync")
outgoing = show_outgoing ? commits_between(remote[:rev], local[:rev]) : []
incoming = show_incoming ? commits_between(local[:rev], remote[:rev]) : []
sync = (incoming.length == 0 && outgoing.length == 0)
verb = case
when incoming.length > 0 &&
outgoing.length > 0 then 'merge'
when incoming.length > 0 then 'pull'
when outgoing.length > 0 then 'push'
else nil
end
cputs sync ? "[x] #{good}" : "[ ] #{bad}"
incoming.each { |c| cputs " &|8&|3<&|9 #{c}" }
outgoing.each { |c| cputs " &|8&|3>&|9 #{c}" }
end
def name(branch)
if $remotes.leaves.include?(branch)
"#{branch[:remote]}/#{branch[:name]}"
else
"#{branch[:name]}"
end
end
cputs "Local Branch: #{name(repo[:current])}"
branch_sync_status(repo[:current], repo[:current][:parent], true, true, "in sync with remote", "out of sync with remote (#{repo[:current][:parent][:remote]}/#{repo[:current][:parent][:name]})") if repo[:current][:parent]
cputs
if repo[:current] == repo[:master]
if repo[:versions].length > 0
cputs "Version branches:"
repo[:versions].each do |branch|
branch_sync_status(repo[:current], branch, false, true, "#{branch[:name]} is merged in", "#{branch[:name]} needs to be merged in")
end
cputs
end
elsif repo[:versions].include?(repo[:current])
master = repo[:master]
cputs "Master Branch: #{name(master)}"
branch_sync_status(repo[:current], master, true, false, "in sync", "#{name(repo[:current])} needs to be merged into master")
cputs
end
if repo[:features].length > 0
cputs "Feature branches:"
repo[:features].each do |branch|
branch_sync_status(repo[:current], branch, false, true, "#{branch[:name]} is merged in", "#{branch[:name]} needs to be merged in")
end
end