commit f8dd1f0b48e1106a62b47cc2927609ca589dc39a tree 4c23a5137714e62c52f22e99b3104122868400ab parent a0710955e70cbceef8cf805645a447f1b370b966 parent b724247612be9f55df7914cb86b64703810d7b73 author administrator 1224499981 +0100 committer administrator 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] [repository] ++## ++## '-d' signifies deletion. 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 . ++## 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 . ++## 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("&", "&").gsub("<", "<").gsub(">", ">") 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 . ++## 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 . ++## 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 <: 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 < .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//. ++## ++## git-wtf Copyright 2008 William Morgan . ++## 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! ++