forked from jwiegley/git-scripts
-
Notifications
You must be signed in to change notification settings - Fork 1
/
git-wtf
executable file
·164 lines (141 loc) · 4.97 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
#!/usr/bin/env ruby
require 'yaml'
CONFIG_FN = ".git-wtfrc"
class Numeric; def pluralize s; "#{to_s} #{s}" + (self != 1 ? "s" : "") end end
def die s
$stderr.puts "Error: #{s}"
exit(-1)
end
$long = ARGV.delete("--long") || ARGV.delete("-l")
## find config file
$config = { "versions" => [], "ignore" => [], "max_commits" => 5 }.merge begin
p = File.expand_path "."
fn = while true
fn = File.join p, CONFIG_FN
break fn if File.exist? fn
pp = File.expand_path File.join(p, "..")
break if p == pp
p = pp
end
if fn
YAML::load_file fn
else
#$stderr.puts "Warning: no config file found. Specify version branches by creating <project_root>/#{CONFIG_FN}"
remotes = `git config --get-regexp ^remote\\.`.split("\n")
die "I can't find your gits" if remotes.empty?
repo = remotes.first =~ /^remote\.(\S+?)\./ ? $1 : "origin"
{ "versions" => %w(master next edge).map { |b| "#{repo}/#{b}" } }
end
end
## the set of commits in 'to' that aren't in 'from'.
## if empty, 'to' has been merged into 'from'.
def commits_between from, to
if $long
`git log --pretty=format:"- %s [%h] (%ae; %ar)" #{from}..#{to}`
else
`git log --pretty=format:"- %s [%h]" #{from}..#{to}`
end.split(/[\r\n]+/)
end
def show_commits commits, label, prefix=""
if commits.empty?
puts "#{prefix}#{label}: none"
else
puts "#{prefix}#{label}:" if label
commits[0 ... $config["max_commits"]].each { |c| puts "#{prefix}#{c}" }
if commits.size > $config["max_commits"]
puts "#{prefix}... and #{commits.size - $config["max_commits"]} more."
end
end
end
def ahead_behind_string ahead, behind
[ahead.empty? ? nil : "#{ahead.size.pluralize 'commit'} ahead",
behind.empty? ? nil : "#{behind.size.pluralize 'commit'} behind"].
compact.join("; ")
end
def show b, all_branches
puts "Local branch: #{b[:local_branch]}"
both = false
if b[:remote_branch]
pushc = commits_between b[:remote_branch], b[:local_branch]
pullc = commits_between b[:local_branch], b[:remote_branch]
both = !pushc.empty? && !pullc.empty?
if pushc.empty?
puts "[x] in sync with remote"
else
action = both ? "push after rebase / merge" : "push"
puts "[ ] NOT in sync with remote (needs #{action})"
show_commits pushc, nil, " "
end
puts "\nRemote branch: #{b[:remote_branch]} (#{b[:remote_url]})"
if pullc.empty?
puts "[x] in sync with local"
else
action = pushc.empty? ? "merge" : "rebase / merge"
puts "[ ] NOT in sync with local (needs #{action})"
show_commits pullc, nil, " "
both = !pushc.empty? && !pullc.empty?
end
end
vbs, fbs = all_branches.partition { |name, br| $config["versions"].include? br[:remote_branch] }
if $config["versions"].include? b[:remote_branch]
puts "\nFeature branches:" unless fbs.empty?
fbs.each do |name, br|
if not `git merge-base #{b[:remote_branch]} #{br[:local_branch]}`.split("\n").empty?
remote_ahead = commits_between b[:remote_branch], br[:local_branch]
local_ahead = commits_between b[:local_branch], br[:local_branch]
if local_ahead.empty? && remote_ahead.empty?
puts "[x] #{br[:name]} is merged in"
elsif local_ahead.empty?
puts "(x) #{br[:name]} merged in (only locally)"
else
behind = commits_between br[:local_branch], b[:remote_branch]
puts "[ ] #{br[:name]} is NOT merged in (#{ahead_behind_string local_ahead, behind})"
show_commits local_ahead, nil, " "
end
end
end
else
puts "\nVersion branches:" unless vbs.empty? # unlikely
vbs.each do |v, br|
ahead = commits_between v, b[:local_branch]
if ahead.empty?
puts "[x] merged into #{v}"
else
behind = commits_between b[:local_branch], v
puts "[ ] NOT merged into #{v}"
end
end
end
puts "\nWARNING: local and remote branches have diverged. A merge will occur unless you rebase." if both
end
branches = `git show-ref`.inject({}) do |hash, l|
sha1, ref = l.chomp.split " refs/"
next hash if $config["ignore"].member? ref
next hash unless ref =~ /^heads\/(.+)/
name = $1
hash[name] = { :name => name, :local_branch => ref }
hash
end
remotes = `git config --get-regexp ^remote\.\*\.url`.inject({}) do |hash, l|
l =~ /^remote\.(.+?)\.url (.+)$/ or next hash
hash[$1] ||= $2
hash
end
`git config --get-regexp ^branch\.`.each do |l|
case l
when /branch\.(.*?)\.remote (.+)/
branches[$1] ||= {}
branches[$1][:remote] = $2
branches[$1][:remote_url] = remotes[$2]
when /branch\.(.*?)\.merge ((refs\/)?heads\/)?(.+)/
branches[$1] ||= {}
branches[$1][:remote_mergepoint] = $4
end
end
branches.each { |k, v| v[:remote_branch] = "#{v[:remote]}/#{v[:remote_mergepoint]}" if v[:remote] && v[:remote_mergepoint] }
targets = if ARGV.empty?
[`git symbolic-ref HEAD`.chomp.sub(/^refs\/heads\//, "")]
else
ARGV
end.map { |t| branches[t] or die "can't find branch #{t.inspect}" }
targets.each { |t| show t, branches }