Skip to content

Instantly share code, notes, and snippets.

@spraints
Created June 7, 2010 18:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save spraints/429008 to your computer and use it in GitHub Desktop.
Save spraints/429008 to your computer and use it in GitHub Desktop.
require 'stringio'
require 'open3'
require 'pp'
$ref = 'HEAD'
unless ARGV.size == 2
puts "Usage: #{$0} <subtree name> <destination branch>"
exit 1
end
$subtree = ARGV.shift.sub(/\/$/, '')
$mirrored_ref = ARGV.shift
def lines(cmd)
StringIO.new(`#{cmd}`).readlines
end
# 'sha of old treeish (commit or root tree)' => 'sha of subtree'
def subtree_infos
$subtree_infos ||= {}
end
def get_subtree(treeish)
subtree_infos[treeish] ||=
`git ls-tree #{treeish} #{$subtree}`.split(/\s/)[2]
end
def parse_commit(lines)
commit = nil
while line = lines.shift
if line =~ /^commit (.*)/
if commit
lines.unshift line
return commit
else
commit = {:ref => $1}
end
elsif line =~ /^tree (.*)/
commit[:tree] = $1
commit[:subtree] = get_subtree $1
elsif line =~ /^parent (.*)/
commit[:parents] = commit[:parents].to_a + [$1]
elsif line =~ /^author (.*) <(.*)> (.*)/
commit[:author] = {
:name => $1,
:email => $2,
:date => $3,
}
elsif line =~ /^\s+(.*\s*)/
commit[:comment] = commit[:comment].to_s + $1
end
end
commit
end
def get_start_info ref
start_commit = `git rev-parse -q --verify #{ref}`
return [] if $?.to_i != 0
start_tree = `git cat-file -p #{ref} | grep ^tree`.split(/\s/)[1]
[start_commit.strip, start_tree]
end
start_new_commit, start_tree = get_start_info $mirrored_ref
commits = []
output = lines("git log --pretty=raw #{$ref} -- #{$subtree}")
while commit = parse_commit(output)
break if start_tree && commit[:subtree] == start_tree
commits.unshift commit
end
class SubtreeExtractor
def initialize(subtree_name)
@subtree_name = subtree_name
end
def extract_subtree commit
#TODO -- subtree needs to be figured out earlier.
#subtree = get_subtree commit[:tree]
subtree = commit[:subtree]
parent_subtrees = commit[:parents].collect { |p| get_subtree p }.compact
new_parent_commits = parent_subtrees.collect { |t| tree_infos[t] }.compact
tree_infos[subtree] = make_new_commit({
:parents => new_parent_commits,
:tree => subtree,
:author => commit[:author],
:comment => commit[:comment],
})
puts "#{subtree} => #{tree_infos[subtree]}"
tree_infos[subtree]
end
# 'sha of subtree' => 'sha of new commit for this subtree'
def tree_infos
@tree_infos ||= {}
end
def make_new_commit(commit)
ENV['GIT_AUTHOR_NAME'] = commit[:author][:name]
ENV['GIT_AUTHOR_EMAIL'] = commit[:author][:email]
ENV['GIT_AUTHOR_DATE'] = commit[:author][:date]
cmd = [%W(git commit-tree),
commit[:tree],
commit[:parents].collect { |p| ['-p', p] }
].flatten
Open3.popen3(*cmd) do |stdin, stdout, stderr|
stdin.write commit[:comment]
stdin.close
stdout.read.strip
end
end
end
c = SubtreeExtractor.new $subtree
c.tree_infos[start_tree] = start_new_commit
new_commit = commits.inject(nil) do |_, commit|
c.extract_subtree commit
end
new_commit ||= $mirrored_ref
puts new_commit
system 'git', 'cat-file', '-p', new_commit
puts "Updating branch #{$mirrored_ref.inspect}..."
system 'git', 'update-ref', "refs/heads/#{$mirrored_ref}", new_commit
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment