redmine-git-remote/app/models/repository/git_remote.rb

146 lines
4.6 KiB
Ruby
Raw Normal View History

2014-11-27 21:48:29 +01:00
require 'redmine/scm/adapters/git_adapter'
require 'pathname'
require 'fileutils'
require 'open3'
2014-11-27 21:48:29 +01:00
2014-11-27 22:07:35 +01:00
class Repository::GitRemote < Repository::Git
2014-11-27 21:48:29 +01:00
PLUGIN_ROOT = Pathname.new(__FILE__).join("../../../..").realpath.to_s
PATH_PREFIX = PLUGIN_ROOT + "/repos/"
before_validation :initialize_clone
# TODO: figure out how to do this safely (if at all)
2014-11-27 21:48:29 +01:00
# before_deletion :rm_removed_repo
# def rm_removed_repo
# if Repository.find_all_by_url(repo.url).length <= 1
# system "rm -Rf #{self.clone_path}"
2014-11-27 21:48:29 +01:00
# end
# end
def extra_clone_url
return nil unless extra_info
extra_info["extra_clone_url"]
end
def clone_url
self.extra_clone_url
end
def clone_path
self.url
end
def clone_host
p = parse(clone_url)
return p[:host]
end
# hook into Repository.fetch_changesets to also run 'git fetch'
def fetch_changesets
puts "Calling fetch changesets on #{clone_path}"
# runs git fetch
self.fetch
super
end
# called in before_validate handler, sets form errors
def initialize_clone
# avoids crash in RepositoriesController#destroy
return unless attributes["extra_info"]["extra_clone_url"]
2014-11-27 21:48:29 +01:00
p = parse(attributes["extra_info"]["extra_clone_url"])
self.identifier = p[:identifier] if identifier.empty?
self.url = PATH_PREFIX + p[:path] if url.empty?
2014-11-28 22:35:33 +01:00
err = ensure_possibly_empty_clone_exists
errors.add :extra_clone_url, err if err
2014-11-27 21:48:29 +01:00
end
# equality check ignoring trailing whitespace and slashes
def two_remotes_equal(a,b)
a.chomp.gsub(/\/$/,'') == b.chomp.gsub(/\/$/,'')
end
2014-11-28 22:35:33 +01:00
def ensure_possibly_empty_clone_exists
2014-11-27 22:07:35 +01:00
Repository::GitRemote.add_known_host(clone_host)
2014-11-27 21:48:29 +01:00
unless system "git", "ls-remote", "-h", clone_url
2014-11-27 21:48:29 +01:00
return "#{clone_url} is not a valid remote."
end
if Dir.exists? clone_path
existing_repo_remote, err, status = Open3::capture3("git", "--git-dir", clone_path, "config", "--get", "remote.origin.url")
return "Unable to run: git --git-dir #{clone_path} config --get remote.origin.url" unless status.success?
2014-11-27 21:48:29 +01:00
unless two_remotes_equal(existing_repo_remote, clone_url)
return "Directory '#{clone_path}' already exits, unmatching clone url: #{existing_repo_remote}"
2014-11-27 21:48:29 +01:00
end
else
unless system "git", "init", "--bare", clone_path
return "Unable to run: git init --bare #{clone_path}"
2014-11-27 21:48:29 +01:00
end
unless system "git", "--git-dir", clone_path, "remote", "add", "--tags", "--mirror=fetch", "origin", clone_url
return "Unable to run: git --git-dir #{clone_path} remote add #{clone_url}"
2014-11-27 21:48:29 +01:00
end
end
end
unloadable
def self.scm_name
2014-11-27 22:07:35 +01:00
'GitRemote'
2014-11-27 21:48:29 +01:00
end
def parse(url)
ret = {}
# start with http://github.com/evolvingweb/git_remote or git@git.ewdev.ca:some/repo.git
ret[:url] = url
# path is github.com/evolvingweb/muhc-ci
ret[:path] = url
.gsub(/^.*:\/\//, '') # Remove anything before ://
.gsub(/:/, '/') # convert ":" to "/"
.gsub(/^.*@/, '') # Remove anything before @
.gsub(/\.git$/, '') # Remove trailing .git
ret[:host] = ret[:path].split('/').first
#TODO: handle project uniqueness automatically or prompt
ret[:identifier] = ret[:path].split('/').last.downcase.gsub(/[^a-z0-9_-]/,'-')
2014-11-27 21:48:29 +01:00
return ret
end
def fetch
puts "Fetching repo #{clone_path}"
2014-11-27 22:07:35 +01:00
Repository::GitRemote.add_known_host(clone_host)
2014-11-27 21:48:29 +01:00
2014-11-28 22:35:33 +01:00
err = ensure_possibly_empty_clone_exists
2014-11-27 21:48:29 +01:00
Rails.logger.warn err if err
# If dir exists and non-empty, should be safe to 'git fetch'
unless system "git", "--git-dir", clone_path, "fetch", "--all"
2014-11-27 21:48:29 +01:00
Rails.logger.warn "Unable to run 'git -c #{clone_path} fetch --all'"
end
end
# Checks if host is in ~/.ssh/known_hosts, adds it if not present
def self.add_known_host(host)
# if not found...
out, status = Open3::capture2("ssh-keygen", "-F", host)
raise "Unable to run 'ssh-keygen -F #{host}" unless status
unless out.match /found/
2014-11-27 21:48:29 +01:00
# hack to work with 'docker exec' where HOME isn't set (or set to /)
ssh_dir = (ENV['HOME'] == "/" || ENV['HOME'] == nil ? "/root" : ENV['HOME']) + "/.ssh"
ssh_known_hosts = ssh_dir + "/known_hosts"
begin
FileUtils.mkdir_p ssh_dir
rescue e
raise "Unable to create directory #{ssh_dir}: " + "\n\n" + e.to_s
end
2014-11-27 21:48:29 +01:00
puts "Adding #{host} to #{ssh_known_hosts}"
out, status = Open3::capture2("ssh-keyscan", host)
raise "Unable to run 'ssh-keyscan #{host}'" unless status
Kernel::open(ssh_known_hosts, 'a') { |f| f.puts out}
2014-11-27 21:48:29 +01:00
end
end
end