Created
November 19, 2012 14:49
-
-
Save gruis/4111040 to your computer and use it in GitHub Desktop.
Migrate nested SVN repositories to Git
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
source :rubygems | |
gem "svn2git", "~>2.2" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# Migrate an SVN project to Git. The repository can contain nested repositories. | |
# Run this script from the directory that you want to contain the git repository. | |
# It will create an authors transform file if one does not exists. | |
require "bundler" | |
Bundler.setup | |
require "fileutils" | |
require "svn2git" | |
def warn(str, padding = "") | |
$stderr.puts "#{padding}\033[32m#{str}\033[0m\n" | |
end | |
def error(str, padding = "") | |
$stderr.puts "#{padding}\033[31m#{str}\033[0m\n" | |
end | |
def abort(str) | |
error(str) | |
Process.exit 2 | |
end | |
class SVN2GitRecursive | |
attr_reader :failures, :root, :svn_root, :authors | |
def initialize(checked_out_svn, root = Dir.pwd, opts = {}) | |
opts, root = root, opts if root.is_a?(Hash) | |
root = Dir.pwd if root.is_a?(Hash) | |
@popd_stack = [] | |
@failures = [] | |
@root = root | |
self.svn_root = checked_out_svn | |
@verbose = opts[:verbose] || "" | |
@authors = File.expand_path("authors-transform.txt") | |
unless File.exists?(@authors) | |
pushd(checked_out_svn) do | |
system %Q{svn log -q | awk -F '|' '/^r/ {sub("^ ", "", $2); sub(" $", "", $2); print $2" = "$2" <"$2">"}' | sort -u > #{@authors}} | |
end | |
abort "please edit #{@authors} to include the names and e-mail addresses of all contributors" | |
end | |
FileUtils.mkdir_p(File.join(root, "repos")) | |
migrate(checked_out_svn) | |
end | |
def svn_root=(svnr) | |
@svn_root = svnr | |
@svn_root += "/" unless @svn_root[-1] == "/" | |
end | |
def svn_url | |
return @svn_url if @svn_url | |
@svn_url = svn_url_for(svn_root) | |
unless @svn_url | |
raise ConfigurationError.new("#{checked_out_svn.path} is not a valid svn repository or does not include a URL attribute") | |
end | |
end | |
def svn_url_for(path) | |
pushd(path) | |
svn_url = `svn info`.split("\n").find{|s| s.start_with?("URL:") } | |
svn_url &&= svn_url.split("URL:",2)[-1].strip | |
popd | |
svn_url | |
end | |
def pushd(path) | |
@popd_stack.push(Dir.pwd) | |
FileUtils.chdir(path) | |
if block_given? | |
begin | |
yield | |
ensure | |
popd | |
end | |
end | |
end | |
def popd | |
raise "popd stack is empty" if @popd_stack.empty? | |
FileUtils.chdir(@popd_stack.pop) | |
end | |
def migrated?(dir) | |
return false if dir == self.svn_root | |
File.exists?(File.join(git_path(dir), ".git")) | |
end | |
def migrate!(projdir, args = "") | |
path = git_path(projdir) | |
warn " migrate #{projdir} to #{path}" | |
FileUtils.mkdir_p(path) | |
succ = pushd(path) do | |
url = svn_url_for(projdir) | |
system("svn2git #{url} --authors #{authors} #{@verbose} #{args}") | |
end | |
unless succ | |
failures << projdir | |
error "failed to migrate #{projdir}" | |
FileUtils.rm_rf(path) | |
end | |
succ | |
end | |
def git_path(projdir) | |
return File.join(@root, "repos") if projdir == self.svn_root || "#{projdir}/" == self.svn_root | |
File.join(@root, "repos", projdir.split(self.svn_root, 2)[-1].gsub("/", "_")) | |
end | |
def migrate(projdir, level = 0) | |
return true if migrated?(projdir) | |
warn "migrating #{projdir}" | |
contents = Dir[File.join(projdir, "*")] | |
if contents.map{|c| File.basename(c) }.include?("trunk") | |
migrate!(projdir, "--no-minimize-url") | |
elsif Dir[File.join(projdir, "**/trunk")].empty? | |
warn "assuming #{projdir} is trunk" | |
migrate!(projdir, "--no-minimize-url --rootistrunk") | |
else | |
contents.each { |subproj| migrate(subproj) } | |
end | |
end | |
end | |
verbose = ARGV.delete("-v") ? "-v" : "" | |
abort "usage: #{File.basenaem($0)} [-v] checked_out_svn_repo" if ARGV.empty? || !File.exists(ARGV[0]) | |
migration = SVN2GitRecursive.new(ARGV[0], :verbose => verbose) | |
unless migration.failures.empty? | |
abort "failed to migrate: #{migration.failures.join(", ")}" | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment