Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

638 lines (635 sloc) 21.778 kB
commit f8dd1f0b48e1106a62b47cc2927609ca589dc39a
tree 4c23a5137714e62c52f22e99b3104122868400ab
parent a0710955e70cbceef8cf805645a447f1b370b966
parent b724247612be9f55df7914cb86b64703810d7b73
author administrator <gmalamid@thoughtworks.com> 1224499981 +0100
committer administrator <gmalamid@thoughtworks.com> 1224499981 +0100
imported William Morgans utilities too, which are very very nice
diff --cc git-publish-branch
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..b28b4fde621dff1c72bcb570183bbe923e4a24b2
new file mode 100755
--- /dev/null
+++ b/git-publish-branch
@@@ -1,0 -1,0 +1,70 @@@
++#!/usr/bin/env ruby
++
++## git-publish-branch: a simple script to ease the unnecessarily complex
++## task of "publishing" a branch, i.e., taking a local branch, creating a
++## reference to it on a remote repo, and setting up the local branch to
++## track the remote one, all in one go. you can even delete that remote
++## reference.
++##
++## Usage: git publish-branch [-d] <branch> [repository]
++##
++## '-d' signifies deletion. <branch> is the branch to publish, and
++## [repository] defaults to "origin". The remote branch name will be the
++## same as the local branch name. Don't make life unnecessarily complex
++## for yourself.
++##
++## Note that unpublishing a branch doesn't delete the local branch.
++## Safety first!
++##
++## git-publish-branch Copyright 2008 William Morgan <wmorgan-git-wt-add@masanjin.net>.
++## This program is free software: you can redistribute it and/or modify
++## it under the terms of the GNU General Public License as published by
++## the Free Software Foundation, either version 3 of the License, or (at
++## your option) any later version.
++##
++## This program is distributed in the hope that it will be useful,
++## but WITHOUT ANY WARRANTY; without even the implied warranty of
++## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++## GNU General Public License for more details.
++##
++## You can find the GNU General Public License at:
++## http://www.gnu.org/licenses/
++
++def exec cmd
++ puts cmd
++ system cmd or die unless $fake
++end
++
++def die s=nil
++ $stderr.puts s if s
++ exit(-1)
++end
++
++head = `git symbolic-ref HEAD`.chomp.gsub(/refs\/heads\//, "")
++delete = ARGV.delete "-d"
++$fake = ARGV.delete "-n"
++branch = (ARGV.shift || head).gsub(/refs\/heads\//, "")
++remote = ARGV.shift || "origin"
++local_ref = `git show-ref heads/#{branch}`
++remote_ref = `git show-ref remotes/#{remote}/#{branch}`
++remote_config = `git config branch.#{branch}.merge`
++
++if delete
++ ## we don't do any checking here because the remote branch might actually
++ ## exist, whether we actually know about it or not.
++ exec "git push #{remote} :refs/heads/#{branch}"
++
++ unless local_ref.empty?
++ exec "git config --unset branch.#{branch}.remote"
++ exec "git config --unset branch.#{branch}.merge"
++ end
++else
++ die "No local branch #{branch} exists!" if local_ref.empty?
++ die "A remote branch #{branch} on #{remote} already exists!" unless remote_ref.empty?
++ die "Local branch #{branch} is already a tracking branch!" unless remote_config.empty?
++
++ exec "git push #{remote} #{branch}:refs/heads/#{branch}"
++ exec "git config branch.#{branch}.remote #{remote}"
++ exec "git config branch.#{branch}.merge refs/heads/#{branch}"
++end
++
diff --cc git-rank-contributors
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..3b272206d185e2f9b8089e6aaa00fde6acc308ef
new file mode 100755
--- /dev/null
+++ b/git-rank-contributors
@@@ -1,0 -1,0 +1,60 @@@
++#!/usr/bin/env ruby
++
++## git-rank-contributors: a simple script to trace through the logs and
++## rank contributors by the total size of the diffs they're responsible for.
++## A change counts twice as much as a plain addition or deletion.
++##
++## Output may or may not be suitable for inclusion in a CREDITS file.
++## Probably not without some editing, because people often commit from more
++## than one address.
++##
++## git-rank-contributors Copyright 2008 William Morgan <wmorgan-git-wt-add@masanjin.net>.
++## This program is free software: you can redistribute it and/or modify
++## it under the terms of the GNU General Public License as published by
++## the Free Software Foundation, either version 3 of the License, or (at
++## your option) any later version.
++##
++## This program is distributed in the hope that it will be useful,
++## but WITHOUT ANY WARRANTY; without even the implied warranty of
++## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++## GNU General Public License for more details.
++##
++## You can find the GNU General Public License at:
++## http://www.gnu.org/licenses/
++
++class String
++ def obfuscate; gsub(/@/, " at the ").gsub(/\.(\w+)(>|$)/, ' dot \1s\2') end
++ def htmlize; gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;") end
++end
++
++lines = {}
++verbose = ARGV.delete("-v")
++obfuscate = ARGV.delete("-o")
++htmlize = ARGV.delete("-h")
++
++author = nil
++state = :pre_author
++`git log -M -C -C -p --no-color`.each do |l|
++ case
++ when (state == :pre_author || state == :post_author) && l =~ /Author: (.*)$/
++ author = $1
++ state = :post_author
++ lines[author] ||= 0
++ when state == :post_author && l =~ /^\+\+\+/
++ state = :in_diff
++ when state == :in_diff && l =~ /^[\+\-]/
++ lines[author] += 1
++ when state == :in_diff && l =~ /^commit /
++ state = :pre_author
++ end
++end
++
++lines.sort_by { |a, c| -c }.each do |a, c|
++ a = a.obfuscate if obfuscate
++ a = a.htmlize if htmlize
++ if verbose
++ puts "#{a}: #{c} lines of diff"
++ else
++ puts a
++ end
++end
diff --cc git-show-merges
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..12907502a6c6885e7183a0e71d9cfeed77917824
new file mode 100755
--- /dev/null
+++ b/git-show-merges
@@@ -1,0 -1,0 +1,49 @@@
++#!/usr/bin/env ruby
++
++## git-show-merges: a simple script to show you which topic branches have
++## been merged into the current branch, and which haven't. (Or, specify
++## the set of merge branches you're interested in on the command line.)
++##
++## git-show-merges Copyright 2008 William Morgan <wmorgan-git-wt-add@masanjin.net>.
++## This program is free software: you can redistribute it and/or modify
++## it under the terms of the GNU General Public License as published by
++## the Free Software Foundation, either version 3 of the License, or (at
++## your option) any later version.
++##
++## This program is distributed in the hope that it will be useful,
++## but WITHOUT ANY WARRANTY; without even the implied warranty of
++## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++## GNU General Public License for more details.
++##
++## You can find the GNU General Public License at:
++## http://www.gnu.org/licenses/
++heads = if ARGV.empty?
++ [`git symbolic-ref HEAD`.chomp]
++else
++ ARGV
++end.map { |r| r.gsub(/refs\/heads\//, "") }
++
++branches = `git show-ref --heads`.
++ scan(/^\S+ refs\/heads\/(\S+)$/).
++ map { |a| a.first }
++
++unknown = heads - branches
++unless unknown.empty?
++ $stderr.puts "Unknown branch: #{unknown.first}"
++ exit(-1)
++end
++
++branches -= heads
++
++heads.each do |h|
++ merged = branches.select { |b| `git log #{h}..#{b}` == "" }
++ unmerged = branches - merged
++
++ puts "merged into #{h}:"
++ merged.each { |b| puts " #{b}" }
++ puts
++ puts "not merged into #{h}: "
++ unmerged.each { |b| puts " #{b}" }
++
++ puts
++end
diff --cc git-wt-add
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..3125ab5495e5f2ab55ed48af5303031fae02c2ac
new file mode 100755
--- /dev/null
+++ b/git-wt-add
@@@ -1,0 -1,0 +1,196 @@@
++#!/usr/bin/env ruby
++
++## git-wt-add: A darcs-style interactive staging script for git. As the
++## name implies, git-wt-add walks you through unstaged changes on a
++## hunk-by-hunk basis and allows you to pick the ones you'd like staged.
++##
++## git-wt-add Copyright 2007 William Morgan <wmorgan-git-wt-add@masanjin.net>.
++## This program is free software: you can redistribute it and/or modify
++## it under the terms of the GNU General Public License as published by
++## the Free Software Foundation, either version 3 of the License, or
++## (at your option) any later version.
++##
++## This program is distributed in the hope that it will be useful,
++## but WITHOUT ANY WARRANTY; without even the implied warranty of
++## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++## GNU General Public License for more details.
++##
++## You can find the GNU General Public License at:
++## http://www.gnu.org/licenses/
++
++COLOR = /\e\[\d*m/
++
++class Hunk
++ attr_reader :file, :file_header, :diff
++ attr_accessor :disposition
++
++ def initialize file, file_header, diff
++ @file = file
++ @file_header = file_header
++ @diff = diff
++ @disposition = :unknown
++ end
++
++ def self.make_from diff
++ ret = []
++ state = :outside
++ file_header = hunk = file = nil
++
++ diff.each do |l| # a little state machine to parse git diff output
++ reprocess = false
++ begin
++ reprocess = false
++ case
++ when state == :outside && l =~ /^(#{COLOR})*diff --git a\/(.+) b\/(\2)/
++ file = $2
++ file_header = ""
++ when state == :outside && l =~ /^(#{COLOR})*index /
++ when state == :outside && l =~ /^(#{COLOR})*(---|\+\+\+) /
++ file_header += l + "\n"
++ when state == :outside && l =~ /^(#{COLOR})*@@ /
++ state = :in_hunk
++ hunk = l + "\n"
++ when state == :in_hunk && l =~ /^(#{COLOR})*(@@ |diff --git a)/
++ ret << Hunk.new(file, file_header, hunk)
++ state = :outside
++ reprocess = true
++ when state == :in_hunk
++ hunk += l + "\n"
++ else
++ raise "unparsable diff input: #{l.inspect}"
++ end
++ end while reprocess
++ end
++
++ ## add the final hunk
++ ret << Hunk.new(file, file_header, hunk) if hunk
++
++ ret
++ end
++end
++
++def help
++ puts <<EOS
++y: record this patch
++n: don't record it
++w: wait and decide later, defaulting to no
++
++s: don't record the rest of the changes to this file
++f: record the rest of the changes to this file
++
++d: record selected patches, skipping all the remaining patches
++a: record all the remaining patches
++q: cancel record
++
++j: skip to next patch
++k: back up to previous patch
++c: calculate number of patches
++h or ?: show this help
++
++<Space>: accept the current default (which is capitalized)
++EOS
++end
++
++def walk_through hunks
++ skip_files, record_files = {}, {}
++ skip_rest = record_rest = false
++
++ while hunks.any? { |h| h.disposition == :unknown }
++ pos = 0
++ until pos >= hunks.length
++ h = hunks[pos]
++ if h.disposition != :unknown
++ pos += 1
++ next
++ elsif skip_rest || skip_files[h.file]
++ h.disposition = :ignore
++ pos += 1
++ next
++ elsif record_rest || record_files[h.file]
++ h.disposition = :record
++ pos += 1
++ next
++ end
++
++ puts "Hunk from #{h.file}"
++ puts h.diff
++ print "Shall I stage this change? (#{pos + 1}/#{hunks.length}) [ynWsfqadk], or ? for help: "
++ c = $stdin.getc
++ puts
++ case c
++ when ?y: h.disposition = :record
++ when ?n: h.disposition = :ignore
++ when ?w, ?\ : h.disposition = :unknown
++ when ?s
++ h.disposition = :ignore
++ skip_files[h.file] = true
++ when ?f
++ h.disposition = :record
++ record_files[h.file] = true
++ when ?d: skip_rest = true
++ when ?a: record_rest = true
++ when ?q: exit
++ when ?k
++ if pos > 0
++ hunks[pos - 1].disposition = :unknown
++ pos -= 2 # double-bah
++ end
++ else
++ help
++ pos -= 1 # bah
++ end
++
++ pos += 1
++ puts
++ end
++ end
++end
++
++def make_patch hunks
++ patch = ""
++ did_header = {}
++ hunks.each do |h|
++ next unless h.disposition == :record
++ unless did_header[h.file]
++ patch += h.file_header
++ did_header[h.file] = true
++ end
++ patch += h.diff
++ end
++
++ patch.gsub COLOR, ""
++end
++
++### execution starts here ###
++
++diff = `git diff`.split(/\r?\n/)
++if diff.empty?
++ puts "No unstaged changes."
++ exit
++end
++hunks = Hunk.make_from diff
++
++## unix-centric!
++state = `stty -g`
++begin
++ `stty -icanon` # immediate keypress mode
++ walk_through hunks
++ensure
++ `stty #{state}`
++end
++
++patch = make_patch hunks
++if patch.empty?
++ puts "No changes selected for staging."
++else
++ IO.popen("git apply --cached", "w") { |f| f.puts patch }
++ puts <<EOS
++Staged patch of #{patch.split("\n").size} lines.
++
++Possible next commands:
++ git diff --cached: see staged changes
++ git commit: commit staged changes
++ git reset: unstage changes
++EOS
++end
++
diff --cc git-wtf
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..ea4b27f6812cf9daf84b047bff4443cc554f92bf
new file mode 100755
--- /dev/null
+++ b/git-wtf
@@@ -1,0 -1,0 +1,223 @@@
++#!/usr/bin/env ruby
++
++## git-wtf: display the state of your repository in a readable and easy-to-scan
++## format.
++##
++## git-wtf tries to ease the task of having many git branches. It's also useful
++## for getting a summary of how tracking branches relate to a remote server.
++##
++## git-wtf shows you:
++## - How your branch relates to the remote repo, if it's a tracking branch.
++## - How your branch relates to non-feature ("version") branches, if it's a
++## feature branch.
++## - How your branch relates to the feature branches, if it's a version branch.
++##
++## For each of these relationships, git-wtf displays the commits pending on
++## either side, if any. It displays checkboxes along the side for easy scanning
++## of merged/non-merged branches.
++##
++## If you're working against a remote repo, git-wtf is best used between a 'git
++## fetch' and a 'git merge' (or 'git pull' if you don't mind the redundant
++## network access).
++##
++## Usage: git wtf [branch+] [-l|--long] [-a|--all] [--dump-config]
++##
++## If [branch] is not specified, git-wtf will use the current branch. With
++## --long, you'll see author info and date for each commit. With --all, you'll
++## see all commits, not just the first 5. With --dump-config, git-wtf will
++## print out its current configuration in YAML format and exit.
++##
++## git-wtf uses some heuristics to determine which branches are version
++## branches, and which are feature branches. (Specifically, it assumes the
++## version branches are named "master", "next" and "edge".) If it guesses
++## incorrectly, you will have to create a .git-wtfrc file.
++##
++## git-wtf looks for a .git-wtfrc file starting in the current directory, and
++## recursively up to the root. The config file is a YAML file that specifies
++## the version branches, any branches to ignore, and the max number of commits
++## to display when --all isn't used. To start building a configuration file,
++## run "git-wtf --dump-config > .git-wtfrc" and edit it.
++##
++## IMPORTANT NOTE: all local branches referenced in .git-wtfrc must be prefixed
++## with heads/, e.g. "heads/master". Remote branches must be of the form
++## remotes/<remote>/<branch>.
++##
++## git-wtf Copyright 2008 William Morgan <wmorgan-git-wt-add@masanjin.net>.
++## This program is free software: you can redistribute it and/or modify it
++## under the terms of the GNU General Public License as published by the Free
++## Software Foundation, either version 3 of the License, or (at your option)
++## any later version.
++##
++## This program is distributed in the hope that it will be useful, but WITHOUT
++## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
++## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
++## more details.
++##
++## You can find the GNU General Public License at: http://www.gnu.org/licenses/
++
++
++require 'yaml'
++CONFIG_FN = ".git-wtfrc"
++
++class Numeric; def pluralize s; "#{to_s} #{s}" + (self != 1 ? "s" : "") end end
++
++$long = ARGV.delete("--long") || ARGV.delete("-l")
++$all = ARGV.delete("--all") || ARGV.delete("-a")
++$dump_config = ARGV.delete("--dump-config")
++
++## find config file
++$config = { "versions" => %w(heads/master heads/next heads/edge), "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
++
++ (fn && YAML::load_file(fn)) || {} # YAML turns empty files into false
++end
++
++if $dump_config
++ puts $config.to_yaml
++ exit(0)
++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, prefix=" "
++ if commits.empty?
++ puts "#{prefix} none"
++ else
++ max = $all ? commits.size : $config["max_commits"]
++ max -= 1 if max == commits.size - 1 # never show "and 1 more"
++ commits[0 ... max].each { |c| puts "#{prefix}#{c}" }
++ puts "#{prefix}... and #{commits.size - max} more." if commits.size > max
++ 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
++ 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
++
++ both = !pushc.empty? && !pullc.empty?
++ end
++ end
++
++ vbs, fbs = all_branches.partition { |name, br| $config["versions"].include? br[:local_branch] }
++ if $config["versions"].include? b[:local_branch]
++ puts "\nFeature branches:" unless fbs.empty?
++ fbs.each do |name, br|
++ remote_ahead = b[:remote_branch] ? 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? && b[:remote_branch]
++ puts "(x) #{br[:name]} merged in (only locally)"
++ else
++ behind = commits_between br[:local_branch], b[:local_branch]
++ puts "[ ] #{br[:name]} is NOT merged in (#{ahead_behind_string local_ahead, behind})"
++ show_commits local_ahead
++ 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} (#{ahead.size.pluralize 'commit'} ahead)"
++ show_commits ahead
++ 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] }
++
++show_dirty = ARGV.empty?
++targets = if ARGV.empty?
++ [`git symbolic-ref HEAD`.chomp.sub(/^refs\/heads\//, "")]
++else
++ ARGV
++end.map { |t| branches[t] or abort "Error: can't find branch #{t.inspect}." }
++
++targets.each { |t| show t, branches }
++
++modified = show_dirty && `git ls-files -m` != ""
++uncommitted = show_dirty && `git diff-index --cached HEAD` != ""
++
++puts if modified || uncommitted
++puts "NOTE: working directory contains modified files" if modified
++puts "NOTE: staging area contains staged but uncommitted files" if uncommitted
++
++# the end!
++
Jump to Line
Something went wrong with that request. Please try again.