Skip to content
Find file
Fetching contributors…
Cannot retrieve contributors at this time
executable file 259 lines (217 sloc) 8.4 KB
#!/usr/bin/env ruby
##
## Author: Steve Purcell, http://www.sanityinc.com/
## Obtain the latest version of this software here: http://git.sanityinc.com/
##
# TODO: import parallel darcs repos as git branches, identifying branch points
require 'ostruct'
require 'rexml/document'
require 'optparse'
# Explicitly setting a time zone would cause darcs to only output in
# that timezone hence we couldn't get the actual patch TZ
# ENV['TZ'] = 'GMT0'
# GIT_DARCS_BRANCH = "darcs_repo" # name of the branch we import to
GIT_PATCHES = ".git/darcs_patches"
options = { :default_email => '' }
opts = OptionParser.new do |opts|
opts.banner = "Creates git repositories from darcs repositories
usage: darcs-to-git DARCSREPODIR [options]
1. Create an *empty* directory that will become the new git repository
2. From inside that directory, run this program, passing the location
of the local source darcs repo as a parameter
The program will git-init the empty directory, and migrate all patches
in the source darcs repo into commits in that repository.
Thereafter, incremental patch conversion from the same source repo is
possible by repeating step 2.
"
opts.on('-e','--default-email ADDRESS',
"Set the email address used when no explicit address is given") do |m|
options[:default_email] = m
end
end
opts.parse!
SRCREPO = ARGV[0]
if SRCREPO.nil? or not FileTest.exists?(SRCREPO + '/_darcs') then
if SRCREPO.nil? then
puts opts.banner()
puts opts.summarize()
else
puts "Argument must be a valid darcs repository"
end
exit(1)
end
def run(*args)
puts "Running: #{args.inspect}"
system(*args) || raise("Failed to run: #{args.inspect}")
end
def output_of(*args)
puts "Running: #{args.inspect}"
output = IO.popen(args.map {|a| "'#{a}'"}.join(' '), 'r') { |p| p.read }
if $?.exitstatus == 0
return output
else
raise "Failed to run: #{args.inspect}"
end
end
# variant of output_of, but you have to check for success on your own
def output_nofail_of(*args)
puts "Running: #{args.inspect}"
output = IO.popen(args.map {|a| "'#{a}'"}.join(' '), 'r') { |p| p.read }
end
class DarcsPatch
attr_accessor :source_repo, :author, :date, :inverted, :identifier, :name, :is_tag, :git_tag_name, :comment
attr_reader :author_name, :author_email
def initialize(source_repo, patch_xml)
self.source_repo = source_repo
self.author = patch_xml.attribute('author').value
self.date = darcs_date_to_git_date(patch_xml.attribute('date').value,
patch_xml.attribute('local_date').value)
self.inverted = (patch_xml.attribute('inverted').to_s == 'True')
self.identifier = patch_xml.attribute('hash').to_s
self.name = patch_xml.get_elements('name').first.get_text.value rescue 'Unnamed patch'
self.comment = patch_xml.get_elements('comment').first.get_text.value rescue nil
if (self.is_tag = (self.name =~ /^TAG (.*)/))
self.git_tag_name = $1.gsub(/[\s:]+/, '_')
end
author_scan
end
def <=>(other)
self.identifier <=> other.identifier
end
def git_commit_message
[ ((inverted ? "UNDO: #{name}" : name) unless name =~ /^\[\w+ @ \d+\]/),
comment
].compact.join("\n\n")
# "darcs-hash:#{identifier}" ].compact.join("\n\n")
end
def self.read_from_repo(repo)
REXML::Document.new(output_of("darcs", "changes", "--reverse", "--repodir=#{repo}", "--xml", "--summary")).get_elements('changelog/patch').map do |p|
DarcsPatch.new(repo, p)
end
end
# Return committish for corresponding patch in current git repo, or false/nil
def id_in_git_repo
@git_commit ||= find_in_git_repo
end
def pull_and_apply
puts "\n" + ("=" * 80)
puts "PATCH : #{name}"
puts "DATE : #{date}"
puts "AUTHOR: #{author_name}"
puts "EMAIL : #{author_email}"
puts "=" * 80
if id_in_git_repo
puts "Already imported to git as #{id_in_git_repo}"
return
end
pull
system("git-status")
apply_to_git_repo
end
private
def author_scan
@author_name, @author_email = if (author =~ /^\s*(\S.*?)\s*\<(\S+@\S+?)\>\s*$/)
[$1, $2]
elsif (author =~ /^\s*\<?(\S+@\S+?)\>?\s*$/)
email = $1
[email.split('@').first, email]
else
[author, options[:default_email]]
end
@author_name = decode_darcs_escapes(@author_name)
# XXX: do the same for names/comments?
end
def decode_darcs_escapes(str)
# darcs uses '[_\hh_]' to quote non-ascii characters where 'h' is
# a hexadecimal. We translate this to '=hh' and use ruby's unpack
# to do replace this with the proper byte.
str.gsub(/\[\_\\(..)\_\]/) { |x| "=#{$1}" }.unpack("M*")[0]
end
def pull
run("darcs", "pull", "--all", "--quiet", "--match", "hash #{identifier}", "--set-scripts-executable", source_repo)
unless `darcs whatsnew -sl` =~ /^No changes!$/
puts "Darcs reports dirty directory: assuming conflict that is fixed by a later patch... reverting"
run("darcs revert --all")
end
end
def apply_to_git_repo
ENV['GIT_AUTHOR_NAME'] = ENV['GIT_COMMITTER_NAME'] = author_name
ENV['GIT_AUTHOR_EMAIL'] = ENV['GIT_COMMITTER_EMAIL'] = author_email
ENV['GIT_AUTHOR_DATE'] = ENV['GIT_COMMITTER_DATE'] = date
if is_tag
run("git-tag", "-a", "-m", git_commit_message, git_tag_name)
else
if (new_files = git_new_files).any?
run(*(["git-add"] + new_files))
end
if git_changed_files.any? || new_files.any?
run("git-commit", "-a", "-m", git_commit_message)
# get full id of last commit and associate it with the patch id
commit_id = output_of("git-log", "-n1").scan(/^commit ([a-z0-9]+$)/).flatten.first
inventory = File.open("#{GIT_PATCHES}", File::WRONLY | File::APPEND | File::CREAT)
inventory.puts "#{identifier} #{commit_id}"
inventory.close
end
end
end
def find_in_git_repo
return nil unless File.exists?(".git/refs/heads/master") # empty repo
if is_tag then
output_of("git-tag", "-l").split(/\r?\n/).include?(git_tag_name) &&
output_of("git-rev-list", "--max-count=1", "tags/#{git_tag_name}").strip
else
output_nofail_of("grep", "#{identifier}", "#{GIT_PATCHES}").scan(/.* ([a-z0-9]+$)/).flatten.first
end
end
def darcs_date_to_git_date(utc,local)
# We ignore the timezone name (it may be ambiguous) and instead
# calculate the timezone offset ourselves
if not utc =~ /^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/ then
raise "Wrong darcs date format"
end
utc_time = Time.utc($1,$2,$3,$4,$5,$6)
# darcs example: Mon Oct 2 14:23:28 CEST 2006
# everything except timezone name is fixed-length
# if parsing failes we just use UTC
if local =~ /^(\w\w\w) (\w\w\w) ([ 1-9]\d) ([ 0-9]\d)\:(\d\d)\:(\d\d) \w* (\d\d\d\d)/ then
local_time = Time.utc($7,$2,$3,$4,$5,$6)
else
local_time = utc_time
end
offs = local_time - utc_time
t = local_time
s = sprintf("%4d-%02d-%02d %02d:%02d:%02d %s%02d%02d", t.year, t.month, t.day,
t.hour, t.min, t.sec,
offs < 0 ? "-" : "+",offs.abs/3600,offs.abs.modulo(3600)/60 )
end
def git_ls_files(wanted)
output_of(*["git-ls-files", "-t", "-o", "-m", "-d", "-X", ".git/info/exclude"]).scan(/^(.?) (.*?)$/m).map do |code, name|
name if wanted.include?(code)
end.compact
end
def git_new_files() git_ls_files(["?"]) end
def git_changed_files() git_ls_files(%w(? R C)) end
end
def darcs_version
output_of(*%w(darcs -v)).scan(/(\d+)\.(\d+)\.(\d+)/).flatten.map {|v| v.to_i}
end
class Array; include Comparable; end
unless darcs_version > [1, 0, 7]
STDERR.write("WARNING: your darcs appears to be old, and may not work with this script\n")
end
unless File.directory?("_darcs")
run("darcs", "init")
run("git-init")
run("touch", "#{GIT_PATCHES}")
File.open(".git/info/exclude", "a") { |f| f.write("_darcs\n.DS_Store\n") }
File.open("_darcs/prefs/boring", "a") { |f| f.write("\\.git$\n\\.DS_Store$\n") }
end
patches = DarcsPatch.read_from_repo(SRCREPO)
patches_to_pull = []
while patch = patches.pop
break if patch.id_in_git_repo
patches_to_pull.unshift(patch)
end
patches_to_pull.each { |patch| patch.pull_and_apply }
puts "\ndarcs import successful. You may now want to run `git gc' to
improve space usage the git repo"
Jump to Line
Something went wrong with that request. Please try again.