├── Makefile
├── README.md
└── git-wtf
/Makefile:
--------------------------------------------------------------------------------
1 | DESTDIR = /usr/local/bin
2 |
3 | .PHONY: default configure install
4 |
5 | default: configure
6 |
7 | configure: git-alias git-sh-alias
8 |
9 | git-alias:
10 | git config --global alias.wtf '!git-wtf'
11 |
12 | git-sh-alias:
13 | echo -e "\n# git-wtf\ngitalias wtf='git wtf'" >> ~/.gitshrc
14 |
15 | install:
16 | install git-wtf ${DESTDIR}
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## git-wtf
2 |
3 | ### Maintenance
4 |
5 | Current known maintainer: Daniel Vartanov
6 | Githup repository: https://github.com/DanielVartanov/willgit
7 | Information page: http://git-wt-commit.rubyforge.org/#git-wtf
8 | Gitorious repository: https://gitorious.org/willgit/mainline (abandoned by William)
9 | Homebrew formula: https://github.com/mxcl/homebrew/blob/master/Library/Formula/willgit.rb
10 | (installation through homebrew is apparently supported)
11 |
12 | git-wtf
is not maintained here. The last known and maintained version of git-wtf
can be found [here](https://github.com/DanielVartanov/willgit). Pull requests and issues should be sent to the main repository.)
13 |
--------------------------------------------------------------------------------
/git-wtf:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | ## git-wtf: display the state of your repository in a readable and easy-to-scan
4 | ## format.
5 | ##
6 | ## git-wtf tries to ease the task of having many git branches. It's also useful
7 | ## for getting a summary of how tracking branches relate to a remote server.
8 | ##
9 | ## git-wtf shows you:
10 | ## - How your branch relates to the remote repo, if it's a tracking branch.
11 | ## - How your branch relates to non-feature ("version") branches, if it's a
12 | ## feature branch.
13 | ## - How your branch relates to the feature branches, if it's a version branch.
14 | ##
15 | ## For each of these relationships, git-wtf displays the commits pending on
16 | ## either side, if any. It displays checkboxes along the side for easy scanning
17 | ## of merged/non-merged branches.
18 | ##
19 | ## If you're working against a remote repo, git-wtf is best used between a 'git
20 | ## fetch' and a 'git merge' (or 'git pull' if you don't mind the redundant
21 | ## network access).
22 | ##
23 | ## Usage: git wtf [branch+] [-l|--long] [-a|--all] [--dump-config]
24 | ##
25 | ## If [branch] is not specified, git-wtf will use the current branch. With
26 | ## --long, you'll see author info and date for each commit. With --all, you'll
27 | ## see all commits, not just the first 5. With --dump-config, git-wtf will
28 | ## print out its current configuration in YAML format and exit.
29 | ##
30 | ## git-wtf uses some heuristics to determine which branches are version
31 | ## branches, and which are feature branches. (Specifically, it assumes the
32 | ## version branches are named "master", "next" and "edge".) If it guesses
33 | ## incorrectly, you will have to create a .git-wtfrc file.
34 | ##
35 | ## git-wtf looks for a .git-wtfrc file starting in the current directory, and
36 | ## recursively up to the root. The config file is a YAML file that specifies
37 | ## the version branches, any branches to ignore, and the max number of commits
38 | ## to display when --all isn't used. To start building a configuration file,
39 | ## run "git-wtf --dump-config > .git-wtfrc" and edit it.
40 | ##
41 | ## IMPORTANT NOTE: all local branches referenced in .git-wtfrc must be prefixed
42 | ## with heads/, e.g. "heads/master". Remote branches must be of the form
43 | ## remotes//.
44 | ##
45 | ## git-wtf Copyright 2008 William Morgan .
46 | ## This program is free software: you can redistribute it and/or modify it
47 | ## under the terms of the GNU General Public License as published by the Free
48 | ## Software Foundation, either version 3 of the License, or (at your option)
49 | ## any later version.
50 | ##
51 | ## This program is distributed in the hope that it will be useful, but WITHOUT
52 | ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
53 | ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
54 | ## more details.
55 | ##
56 | ## You can find the GNU General Public License at: http://www.gnu.org/licenses/
57 |
58 |
59 | require 'yaml'
60 | CONFIG_FN = ".git-wtfrc"
61 |
62 | class Numeric; def pluralize s; "#{to_s} #{s}" + (self != 1 ? "s" : "") end end
63 |
64 | $long = ARGV.delete("--long") || ARGV.delete("-l")
65 | $all = ARGV.delete("--all") || ARGV.delete("-a")
66 | $dump_config = ARGV.delete("--dump-config")
67 |
68 | ## find config file
69 | $config = { "versions" => %w(heads/master heads/next heads/edge), "ignore" => [], "max_commits" => 5 }.merge begin
70 | p = File.expand_path "."
71 | fn = while true
72 | fn = File.join p, CONFIG_FN
73 | break fn if File.exist? fn
74 | pp = File.expand_path File.join(p, "..")
75 | break if p == pp
76 | p = pp
77 | end
78 |
79 | (fn && YAML::load_file(fn)) || {} # YAML turns empty files into false
80 | end
81 |
82 | if $dump_config
83 | puts $config.to_yaml
84 | exit(0)
85 | end
86 |
87 | ## the set of commits in 'to' that aren't in 'from'.
88 | ## if empty, 'to' has been merged into 'from'.
89 | def commits_between from, to
90 | if $long
91 | `git log --pretty=format:"- %s [%h] (%ae; %ar)" #{from}..#{to}`
92 | else
93 | `git log --pretty=format:"- %s [%h]" #{from}..#{to}`
94 | end.split(/[\r\n]+/)
95 | end
96 |
97 | def show_commits commits, prefix=" "
98 | if commits.empty?
99 | puts "#{prefix} none"
100 | else
101 | max = $all ? commits.size : $config["max_commits"]
102 | max -= 1 if max == commits.size - 1 # never show "and 1 more"
103 | commits[0 ... max].each { |c| puts "#{prefix}#{c}" }
104 | puts "#{prefix}... and #{commits.size - max} more." if commits.size > max
105 | end
106 | end
107 |
108 | def ahead_behind_string ahead, behind
109 | [ahead.empty? ? nil : "#{ahead.size.pluralize 'commit'} ahead",
110 | behind.empty? ? nil : "#{behind.size.pluralize 'commit'} behind"].
111 | compact.join("; ")
112 | end
113 |
114 | def show b, all_branches
115 | puts "Local branch: #{b[:local_branch]}"
116 | both = false
117 |
118 | if b[:remote_branch]
119 | pushc = commits_between b[:remote_branch], b[:local_branch]
120 | pullc = commits_between b[:local_branch], b[:remote_branch]
121 |
122 | both = !pushc.empty? && !pullc.empty?
123 | if pushc.empty?
124 | puts "[x] in sync with remote"
125 | else
126 | action = both ? "push after rebase / merge" : "push"
127 | puts "[ ] NOT in sync with remote (needs #{action})"
128 | show_commits pushc
129 | end
130 |
131 | puts "\nRemote branch: #{b[:remote_branch]} (#{b[:remote_url]})"
132 |
133 | if pullc.empty?
134 | puts "[x] in sync with local"
135 | else
136 | action = pushc.empty? ? "merge" : "rebase / merge"
137 | puts "[ ] NOT in sync with local (needs #{action})"
138 | show_commits pullc
139 |
140 | both = !pushc.empty? && !pullc.empty?
141 | end
142 | end
143 |
144 | vbs, fbs = all_branches.partition { |name, br| $config["versions"].include? br[:local_branch] }
145 | if $config["versions"].include? b[:local_branch]
146 | puts "\nFeature branches:" unless fbs.empty?
147 | fbs.each do |name, br|
148 | remote_ahead = b[:remote_branch] ? commits_between(b[:remote_branch], br[:local_branch]) : []
149 | local_ahead = commits_between b[:local_branch], br[:local_branch]
150 | if local_ahead.empty? && remote_ahead.empty?
151 | puts "[x] #{br[:name]} is merged in"
152 | elsif local_ahead.empty? && b[:remote_branch]
153 | puts "(x) #{br[:name]} merged in (only locally)"
154 | else
155 | behind = commits_between br[:local_branch], b[:local_branch]
156 | puts "[ ] #{br[:name]} is NOT merged in (#{ahead_behind_string local_ahead, behind})"
157 | show_commits local_ahead
158 | end
159 | end
160 | else
161 | puts "\nVersion branches:" unless vbs.empty? # unlikely
162 | vbs.each do |v, br|
163 | ahead = commits_between v, b[:local_branch]
164 | if ahead.empty?
165 | puts "[x] merged into #{v}"
166 | else
167 | #behind = commits_between b[:local_branch], v
168 | puts "[ ] NOT merged into #{v} (#{ahead.size.pluralize 'commit'} ahead)"
169 | show_commits ahead
170 | end
171 | end
172 | end
173 |
174 | puts "\nWARNING: local and remote branches have diverged. A merge will occur unless you rebase." if both
175 | end
176 |
177 | #Required for Ruby 1.9+ as string arrays are handled differently
178 | unless String.method_defined?(:lines) then
179 | class String
180 | def lines
181 | to_a
182 | end
183 | end
184 | end
185 |
186 | branches = `git show-ref`.lines.to_a.inject({}) do |hash, l|
187 | sha1, ref = l.chomp.split " refs/"
188 | next hash if $config["ignore"].member? ref
189 | next hash unless ref =~ /^heads\/(.+)/
190 | name = $1
191 | hash[name] = { :name => name, :local_branch => ref }
192 | hash
193 | end
194 |
195 | remotes = `git config --get-regexp ^remote\.\*\.url`.lines.to_a.inject({}) do |hash, l|
196 | l =~ /^remote\.(.+?)\.url (.+)$/ or next hash
197 | hash[$1] ||= $2
198 | hash
199 | end
200 |
201 | `git config --get-regexp ^branch\.`.lines.to_a.each do |l|
202 | case l
203 | when /branch\.(.*?)\.remote (.+)/
204 | next if $2 == '.'
205 |
206 | branches[$1] ||= {}
207 | branches[$1][:remote] = $2
208 | branches[$1][:remote_url] = remotes[$2]
209 | when /branch\.(.*?)\.merge ((refs\/)?heads\/)?(.+)/
210 | branches[$1] ||= {}
211 | branches[$1][:remote_mergepoint] = $4
212 | end
213 | end
214 |
215 | branches.each { |k, v| v[:remote_branch] = "#{v[:remote]}/#{v[:remote_mergepoint]}" if v[:remote] && v[:remote_mergepoint] }
216 |
217 | show_dirty = ARGV.empty?
218 | targets = if ARGV.empty?
219 | [`git symbolic-ref HEAD`.chomp.sub(/^refs\/heads\//, "")]
220 | else
221 | ARGV
222 | end.map { |t| branches[t] or abort "Error: can't find branch #{t.inspect}." }
223 |
224 | targets.each { |t| show t, branches }
225 |
226 | modified = show_dirty && `git ls-files -m` != ""
227 | uncommitted = show_dirty && `git diff-index --cached HEAD` != ""
228 |
229 | puts if modified || uncommitted
230 | puts "NOTE: working directory contains modified files" if modified
231 | puts "NOTE: staging area contains staged but uncommitted files" if uncommitted
232 |
233 | # the end!
234 |
235 |
--------------------------------------------------------------------------------