forked from ddollar/git-utils
-
Notifications
You must be signed in to change notification settings - Fork 0
/
git-wtf
executable file
·210 lines (178 loc) · 5.9 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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
#!/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)
rescue
$config = {}
ensure
$config['version_branches'] ||= []
$config['alternate_branches'] ||= {}
end
current_branch = run_command("git branch").split("\n").detect { |l| l[0,1] == '*' }[2..-1]
$branches = run_command("git for-each-ref refs/heads").split("\n").inject({}) do |hash, line|
parts = line.split(' ')
name = parts.last.split('/').last
rev = parts.first
current = (name == current_branch)
version = $config["version_branches"].include?(name)
alt = $config["alternate_branches"][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,
:alt => alt,
:merge => { :remote => remote, :branch => merge }
}})
end
$remotes = run_command("git for-each-ref refs/remotes").split("\n").inject({}) do |hash, line|
parts = line.split(' ')
ref_parts = parts.last.split('/')
name = ref_parts.pop
remote = ref_parts.pop
rev = parts.first
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] },
:alts => $branches.values.select { |b| b[:alt] },
:features => $branches.values.select { |b| !(b[:version] || b[:alt] || 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[: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
# elsif repo[:alts].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[:versions].length > 0
cputs "Version branches:"
repo[:versions].each do |branch|
branch_sync_status(repo[:master], branch, false, true, "#{branch[:name]} is merged in to master", "#{branch[:name]} needs to be merged in to master")
end
cputs
end
if repo[:alts].length > 0
master = repo[:master]
cputs "Alternate branches:"
repo[:alts].each do |branch|
parent = $branches[branch[:alt]]
branch_sync_status(parent, branch, true, false, "#{branch[:name]} is updated from #{parent[:name]}", "#{branch[:name]} needs to be updated from #{parent[:name]}")
end
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