rewrite git history with git edit

I’m currently doing a lot of history rewrites to keep my branch clean (tons of changes that I rebase regularly)

So I built git-edit

Usage

git-edit abf1234
... do some work ...
git-edit --amend # amend changes to abf1234 and finish
# or if things go south ...
git-edit --abort  # get me out of here 🙂

Install
Install ruby.

curl https://raw.github.com/grosser/dotfiles/master/bin/git-edit >\
 ~/bin/git-edit && chmod +x ~/bin/git-edit

Remember: Do not use unless you are the only one working on this branch!

Automatic pull, merge and push for git feature branches

We use this script to automatically merge all features into staging and re-merge those features with deploy-ed code. It will stop when there is a merge conflict -> resolve and start it again.

Automatic –>
often -> smaller conflicts.
no thinking/typing -> less mistakes/headaches.

Usage
e.g. store it in scripts folder or turn it into a rake task

scripts/push_and_merge_all
scripts/push_and_merge_all stage # to also deploy everything to staging

Code

#!/usr/bin/env ruby
require 'rubygems'
require 'rake'

sh "git status | grep 'nothing to commit' && echo 'you are clean''" # ensure we are not dirty
current_branch = `git branch | grep '*'`.split.last
sh "git fetch origin" # get up-to-date info about what needs pulling

def merge(branch, options)
  sh "git checkout #{branch}"
  push_and_pull
  options[:with].each do |merged_branch|
    sh "git checkout #{merged_branch}"
    push_and_pull
    sh "git checkout #{branch} && git merge #{merged_branch}"
  end
  push_and_pull
end

def push_and_pull
  status= `git status 2> /dev/null`
  remote_pattern = /# Your branch is (.*?) /
  diverge_pattern = /# Your branch and (.*) have diverged/
  if status =~ remote_pattern
    if $1 == 'ahead'
      sh "git push"
    else
      sh "git pull"
    end
  elsif status =~ diverge_pattern
    sh "git pull && git push"
  end
end

to_stage = ['master', 'feature_a', 'feature_b']
to_stage.each{|branch| merge(branch, :with => ['deploy']) }
merge('staging', :with => to_stage)

sh "cap deploy" if ARGV[0] == 'stage'
sh "git checkout #{current_branch}"

What are your git-stats?

Also available as Gist

Ever wondered how much who adds/removes, its time to find out 😀
(those are real stats, I just obfuscated the names 😉 )

Results

Git scores (in LOC):
mr-add              :  +482273       -9466       
justu               :  +286250       -159905     
grosser             :  +152384       -323344     
another             :  +121257       -82116      
naames              :  +104577       -13591      
justfor             :  +68716        -72446      
example             :  +7795         -4987       
andeven             :  +5100         -1730       
morenow             :  +4225         -2764       
finish              :  +17           -19       

Update: After working 2.5 years on this project my final stats where: +861345 -1115685 = -254340 LOC 😀

Install
Copy init git_stats.rb and ruby git_stats.rb
(you can add the names of people who commit with different users into the ‘same’ array)

#!/usr/bin/env ruby
# please add enhancements to http://gist.github.com/234560
require 'optparse'
start = Time.now.to_i
sort_by = '+'
STDOUT.sync = true

same = [
  ['my name','my-name']
]

# parse options
options = {}
OptionParser.new do |opts|
  opts.banner = <<BANNER
Show git stats, options are:
BANNER
  opts.on("--limit [LIMIT]", Integer, "Limit"){|x| options[:limit] = x }
  opts.on("--max-lines [SIZE]", Integer, "Max lines per commit  ignore large commits"){|x| options[:max_lines] = x }
  opts.on("--help", "Show help"){ puts self; exit }
end.parse!

# parse commits
pipe = open('|git log --pretty=format:"A:%an" --shortstat --no-merges')
authors = ["unknown"]
stats = {}

count = 0
loop do
  count += 1
  break if options[:limit] and count/2 > options[:limit] # break if limit was reached (2 lines per commit)
  line = pipe.readline rescue break

  if line =~ /^A\:(.*?)$/
    authors = $1.strip
    found = same.detect{|a| a.include?(authors)}
    authors = found.first if found
    authors = authors.split('&').map(&:strip) # split up multiple people
    next
  end

  if line =~ /files changed, (\d+) insertions\(\+\), (\d+) deletions/
    add = $1.to_i
    remove = $2.to_i
    if options[:max_lines] and (add + remove) > options[:max_lines]
      print 'x'
    else
      authors.each do |author|
        stats[author] ||= Hash.new(0)
        stats[author]['+'] += add
        stats[author]['-'] += remove
      end
      print '.'
    end
  end
end

longest = stats.map{|a,d|a.length}.max
stats = stats.sort_by{|a,d| -d[sort_by] }
stats = stats.map do |author, data|
  "#{author.ljust(longest)}:  +#{data['+'].to_s.ljust(10)}   -#{data['-'].to_s.ljust(10)} "
end

puts "\nGit scores (in LOC):"
puts stats.join("\n")
puts "Took #{Time.now.to_i - start} seconds"