├── 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 | --------------------------------------------------------------------------------