Skip to content

Instantly share code, notes, and snippets.

@gruis
Created November 19, 2012 14:49
Show Gist options
  • Save gruis/4111040 to your computer and use it in GitHub Desktop.
Save gruis/4111040 to your computer and use it in GitHub Desktop.
Migrate nested SVN repositories to Git
source :rubygems
gem "svn2git", "~>2.2"
#!/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