Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Tree: 8fb81c8eaf
Fetching contributors…

Cannot retrieve contributors at this time

executable file 225 lines (185 sloc) 7.449 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
# TODO: recapture time zone: http://www.timeanddate.com/library/abbreviations/timezones/
# TODO: always use darcs branch
require 'ostruct'
require 'rexml/document'
ENV['TZ'] = 'GMT0'
# GIT_DARCS_BRANCH = "darcs_repo" # name of the branch we import to
GIT_PATCHES = ".git/darcs_patches"
SRCREPO = ARGV[0]
if [nil, '--help', '-h'].include?(SRCREPO)
STDERR.write(<<-end_usage)
Creates git repositories from darcs repositories
usage: darcs-to-git DARCSREPODIR
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.
end_usage
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').to_s)
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, ''] # Could manufacture or insert email address here
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
#output_of("git-log", "-n1", "--grep=darcs-hash:#{identifier}").scan(/^commit ([a-z0-9]+$)/).flatten.first
end
end
def darcs_date_to_git_date(d)
d.gsub(/^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)\d+$/, '\1-\2-\3 \4:\5')
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.