summaryrefslogtreecommitdiffstats
path: root/genome-sync
diff options
context:
space:
mode:
authorChris Alfonso <calfonso@redhat.com>2008-07-07 17:20:43 -0400
committerChris Alfonso <calfonso@redhat.com>2008-07-08 10:43:59 -0400
commit40598cb7bfeb6b1042482fe91701770b179c1803 (patch)
tree57d9b019a845fbcf4a7fcb8ccbce836bfc45174c /genome-sync
parent9b9d481f6684777e90bb144193251244d14926ae (diff)
downloadtools-40598cb7bfeb6b1042482fe91701770b179c1803.tar.gz
tools-40598cb7bfeb6b1042482fe91701770b179c1803.tar.xz
tools-40598cb7bfeb6b1042482fe91701770b179c1803.zip
Renaming everying everest to genome
Diffstat (limited to 'genome-sync')
-rw-r--r--genome-sync/Makefile35
-rw-r--r--genome-sync/Rakefile43
-rw-r--r--genome-sync/bin/genome-sync373
-rw-r--r--genome-sync/extra/genome-sync.spec54
-rw-r--r--genome-sync/lib/genome-sync.rb43
5 files changed, 548 insertions, 0 deletions
diff --git a/genome-sync/Makefile b/genome-sync/Makefile
new file mode 100644
index 0000000..0f457d8
--- /dev/null
+++ b/genome-sync/Makefile
@@ -0,0 +1,35 @@
+NAME := genome-sync
+SPECFILE = extra/$(NAME).spec
+VERSION := $(shell rpm -q --qf "%{VERSION}\n" --specfile $(SPECFILE)| head -1)
+RELEASE := $(shell rpm -q --qf "%{RELEASE}\n" --specfile $(SPECFILE)| head -1)
+UPSTREAM_NAME = $(PROJECT)
+
+TAG = $(subst .,_,$(NAME)-$(VERSION)-$(RELEASE))
+
+CVS = cvs
+RPMBUILD = rpmbuild
+INSTALL = /usr/bin/install
+INSTALL_DIR = $(INSTALL) --verbose -d -m 755
+
+RPM_TOPDIR = /tmp/$(NAME)-$(VERSION)-$(RELEASE)-build
+_RPM_OPTS = --define "_topdir $(RPM_TOPDIR)" \
+ --define "_builddir %{_topdir}" \
+ --define "_sourcedir $(shell pwd)/pkg" \
+ --define "_specdir $(shell pwd)" \
+ --define "_rpmdir $(shell pwd)" \
+ --define "_srcrpmdir $(shell pwd)" \
+ --define '_rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm'
+RPM_OPTS = $(strip $(_RPM_OPTS))
+
+rpm: clean gem $(RPM_TOPDIR) $(SPECFILE)
+ $(RPMBUILD) --clean $(RPM_OPTS) -bb $(SPECFILE)
+
+gem:
+ rake package
+
+clean:
+ @rm -rfv *~ *.rpm $(RPM_TOPDIR) $(ARCHIVE)
+ rake clobber package
+
+$(RPM_TOPDIR):
+ @$(INSTALL_DIR) $@
diff --git a/genome-sync/Rakefile b/genome-sync/Rakefile
new file mode 100644
index 0000000..e651e09
--- /dev/null
+++ b/genome-sync/Rakefile
@@ -0,0 +1,43 @@
+# -*- ruby -*-
+require 'rubygems'
+Gem::manage_gems
+require 'rake/gempackagetask'
+require './lib/genome-sync'
+
+spec = Gem::Specification.new do |s|
+ s.platform = Gem::Platform::RUBY
+ s.name = GenomeSync::AppName
+ s.version = GenomeSync::Version::STRING
+ s.author = "Red Hat IT"
+ s.email = "genome-list@redhat.com"
+ s.summary = "A tool for syncronizing Genome Repositories"
+ s.files = ["lib/#{GenomeSync::AppName}.rb", "extra/#{GenomeSync::AppName}.spec" ]
+ s.executables = [GenomeSync::AppName]
+ s.require_path = "lib"
+ s.add_dependency('git', '>= 1.0.7.1')
+ s.add_dependency('main')
+ s.add_dependency('highline')
+ #s.test_files = Dir.glob('tests/*.rb')
+ #s.has_rdoc = true
+ #s.extra_rdoc_files = ["README"]
+end
+
+Rake::GemPackageTask.new(spec) do |pkg|
+ pkg.need_tar = true
+end
+
+task :default => "pkg/#{spec.name}-#{spec.version}.gem" do
+ puts "generated latest version"
+end
+
+desc "Regenerate Documentation"
+task :doc do |t|
+ system('rdoc lib/ README --main README --inline-source')
+end
+
+#desc "Run Unit Tests"
+#task :test do |t|
+# require File.dirname(__FILE__) + '/tests/all_tests.rb'
+#end
+
+# vim: syntax=Ruby
diff --git a/genome-sync/bin/genome-sync b/genome-sync/bin/genome-sync
new file mode 100644
index 0000000..909c43d
--- /dev/null
+++ b/genome-sync/bin/genome-sync
@@ -0,0 +1,373 @@
+#!/usr/bin/env ruby
+require 'open-uri'
+require 'fileutils'
+require 'git'
+require 'main'
+require 'highline/import'
+require 'genome-sync'
+#http://www.mail-archive.com/capistrano@googlegroups.com/msg01822.html
+HighLine.track_eof = false
+
+include GenomeSync
+
+Main {
+ version Version::STRING
+ description "A tool for syncronizing Genome Repo machines."
+
+ # Normally I wouldn't use class variables but I think it's warranted within
+ # the 'main' DSL. If there are any other global config variables feel free
+ # to put them here.
+ APP_NAME = "genome-sync"
+ DEFAULT_WORKING_DIR = File.join(ENV["HOME"], "." + APP_NAME)
+
+ @@repo_path_prefix = "/pub/git"
+ @@puppetmaster_base_dir = "/etc/puppet/modules/main"
+
+ # Main hook
+ def handle_exception e
+ puts e.message
+ end unless $DEBUG
+
+ option('verbose', 'v')
+
+ option('workingdir', 'w') {
+ description "The working directory to sync"
+ default DEFAULT_WORKING_DIR
+ attr
+ argument_required
+ }
+
+ def run
+ require 'rbconfig'
+ script = File.join(Config::CONFIG['bindir'], APP_NAME)
+ exec("#{script} --help")
+ end
+
+ mode('clean') {
+ def run()
+ verbose("Removing #{workingdir}")
+ FileUtils.rm_rf(workingdir)
+ end
+ }
+
+ mode('start') {
+ description "Guides the user through the syncronization process"
+
+ option('repo', 'r'){
+ description "Genome Repository to syncronize with"
+ required
+ argument_required
+ }
+
+ mode('quick') {
+ description "Perform's hard reset to a given Genome Repo."
+
+ def run
+ start do |git|
+ git_action(git, :non_interactive => true) do |b|
+ git.reset_hard(b)
+ end
+ end
+ end
+ }
+
+ def run
+ start(:interactive => true) do |git|
+ # Begin YACW (Yet Another Cheesy Wizard)
+ choose do |menu|
+ menu.prompt = "What would you like to do?"
+
+ menu.choice "'reset --hard' to the remote" do
+ git_action(git) do |b|
+ git.reset_hard(b)
+ end
+ end
+
+ menu.choice "merge" do
+ git_action(git) do |b|
+ git.merge(b)
+ end
+ end
+
+ menu.choice "rebase" do
+ git_action(git) do |b|
+ git.rebase(b)
+ end
+ end
+
+ menu.choice "something even more exciting" do
+ git_action(git) do |b|
+ fork do
+ say("Type 'exit' when you are done to continue")
+ git.chdir {exec('bash')}
+ end
+
+ Process.wait2
+ end
+ end
+ end
+ end
+ end
+ }
+
+ mode('save') {
+ description "Push the changes in the working directory to #{@@repo_path_prefix}.\n" +
+ "The puppet modules will have their 'master' branches\n" +
+ "checked out to #{@@puppetmaster_base_dir}."
+
+ def run
+ working_git_dirs.each do |d|
+ repo_base_path = d.sub(workingdir, "") # Chop off the working dir
+ dest = File.join(@@repo_path_prefix, repo_base_path)
+ bare_repo_name = File.basename(dest)
+ bare_repo_parent_dir = File.dirname(dest)
+
+ unless File.directory?(dest)
+ verbose("Making #{bare_repo_parent_dir}")
+ FileUtils.mkdir_p(bare_repo_parent_dir)
+
+ verbose("Cloning #{d} to #{dest}")
+
+ # Handle the special puppet module case
+ if repo_base_path =~ %r-^/?puppet-
+
+ # For the correct code to make it to the puppetmaster we need to
+ # make sure 'master' is the active branch.
+ Git.open(d).checkout('master')
+
+ puppetmaster_dir = File.join(@@puppetmaster_base_dir, bare_repo_name)
+ verbose("Checking out 'master' at #{puppetmaster_dir}")
+ Git.clone(d, puppetmaster_dir)
+ # HACK: I couldn't get the git gem to clone the working dir for the
+ # puppetmaster and the bare repo for gitweb. This is a workaround.
+ FileUtils.mv(File.join(puppetmaster_dir, ".git"), dest)
+
+ # Lay down hook to update the puppetmaster
+ hook_file = File.join(dest, "hooks/post-receive")
+ File.open(hook_file, "w") do |f|
+ verbose("Laying down #{hook_file}")
+ f.puts(puppet_post_receive_hook)
+ end
+ File.chmod(0744, hook_file)
+ else
+ Git.clone(d,
+ bare_repo_name,
+ :path => bare_repo_parent_dir,
+ :bare => true)
+ end
+ end
+
+ verbose("Pushing all branches in #{d} to #{dest}:")
+ git = Git.open(d)
+ git.branches.local.each do |b|
+ b.checkout
+ verbose("#{b}")
+ git.push(dest,b, :force => true)
+ end
+ verbose("done.")
+ end
+ end
+ }
+
+ ##############################################################################
+ # These methods are available to all modes since their in the global namespace
+ ##############################################################################
+
+ def verbose(msg)
+ puts msg if params['verbose'].given?
+ end
+
+ # I couldn't find a way to 'checkout -b --force' so this will have to do for
+ # now. It would be better to work this into the git gem at some point.
+ def checkout_b(git, branch, opts = {})
+ verbose("Setting #{branch.name} to #{git.object(branch).sha}")
+ git.branch(branch.name).checkout
+ git.reset_hard(branch)
+ end
+
+ def working_git_dirs
+ Dir[workingdir + "/**/.git"].map {|g| File.dirname(g)}
+ end
+
+ # Just a helper method. Handle recovery
+ def git_action_safe(g, b)
+ local_branches = g.branches.local
+
+ # See if the branch already exists
+ if lb = local_branches.find {|l| l.name == b.name}
+ lb.checkout
+ begin
+ yield(b)
+ rescue Git::GitExecuteError => e
+ fork do
+ puts e.message
+ puts "Type 'exit' when everything is fixed to continue '#{APP_NAME}'"
+ g.chdir {exec('bash')}
+ end
+
+ Process.wait2
+ end
+ else # We must create the branch
+ checkout_b(g,b)
+ end
+ end
+
+ # Abstracts the UI for all git modes. Regardless or whether you are
+ # merging, rebasing, etc we want the user to be presented with the same choices.
+ # Namely, they can select a branch if there are multiple remote branches and with
+ # that branch they can do whatever action is contained in the 'block'--all the while
+ # recovering from git failures.
+ def git_action(g, opts={}, &block)
+ remote_branches = g.branches.find_all do |b|
+ (b.remote.to_s == @genome_repo) && b.name != 'HEAD'
+ end
+
+ # This is a horrible hack. The interactiveness should not be this method's
+ # concern.
+ if opts[:non_interactive]
+ remote_branches.each do |b|
+ git_action_safe(g, b, &block)
+ end
+ else
+ # Don't bother asking the user for a branch if there is only one
+ if remote_branches.size == 1
+ git_action_safe(g, remote_branches[0], &block)
+ else
+ choose do |m|
+ m.prompt = "Select branch."
+
+
+ m.choices(*remote_branches) do |b|
+ git_action_safe(g, b, &block)
+ end
+
+ m.choice("all branches") do
+ remote_branches.each do |b|
+ git_action_safe(g, b, &block)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ def start(opts={})
+ # Setup
+ verbose "making #{workingdir}"
+ FileUtils.mkdir_p(workingdir)
+
+ @genome_repo = params['repo'].value
+
+ verbose("Finding available repos on #{@genome_repo}")
+ open("http://#{@genome_repo}/git/gitweb.cgi?a=project_index") do |f|
+ # Sometimes gitweb attaches the owner's name of the repo. The regex
+ # should grab just the part we need.
+ @git_repo_paths = f.readlines.map {|line| line.strip.match(/([^\s]*)/)[1]}
+ end
+
+ # Iterate over each repo and guide the user through the sync process
+ @git_repo_paths.each do |p|
+ next unless agree("Sync #{p}? (y/n)", true) if opts[:interactive]
+
+ clone_dir = File.join(workingdir, p)
+ verbose "clone_dir = #{clone_dir}"
+
+ # Handle the case where the repo path is foo/bar
+ dirs = File.split(p)
+ if dirs.size > 1
+ subdirs = dirs[0..-2]
+ repo_parent_dir = workingdir + File::SEPARATOR + File.join(subdirs)
+ verbose "Making #{repo_parent_dir}"
+ FileUtils.mkdir_p(repo_parent_dir)
+ end
+
+ repo_path = "#{@@repo_path_prefix}/#{p}"
+ repo_url = "git://#{@genome_repo + repo_path}"
+
+ # We need to clone from the remote if the local clone doesn't exist
+ git = if File.directory?(clone_dir)
+ verbose "Opening #{clone_dir}"
+ Git.open(clone_dir)
+ else
+ # NOTE: For symplicity's sake I decided not to bother cloning from /pub/git
+ # locally if the repo does not exist in the working dir. This would
+ # make the tool have to detect if the local clone is out sync. While
+ # this isn't hard, I don't feel like messing with it at the moment.
+ verbose("Cloning #{repo_url} to #{clone_dir}")
+ g = Git.clone(repo_url, clone_dir)
+
+ g.branches.remote.each do |r|
+ next if r.name == 'HEAD'
+ checkout_b(g, r)
+ end
+
+ # Don't bother asking the user anything else about this repo if we
+ # don't even have a local public copy yet.
+ unless File.directory?(repo_path)
+ verbose("No local public repo found at #{repo_path}")
+ next
+ end
+
+ g # value from if
+ end
+
+ ##############################
+ # Setup Remotes:
+ ##############################
+
+ # Don't add the remote if it already exists
+ # Note: By convention the remote must be the same as the genome repo fqdn
+ git.add_remote(@genome_repo, repo_url) unless git.remotes.find {|r| r.name == @genome_repo}
+ verbose("Fetching #{@genome_repo}")
+ git.fetch(@genome_repo)
+
+ # If we have a public repo make sure we have a remote that points to it
+ localcache_remote_name = 'localcache'
+ if File.directory?(repo_path) # This dir will exist if the user has ever used 'save'
+ # Add a remote if we don't have one
+ unless git.remotes.find {|r| r.name == localcache_remote_name}
+ git.add_remote(localcache_remote_name, repo_path)
+ end
+
+ verbose("Fetching #{localcache_remote_name}")
+ git.fetch(localcache_remote_name)
+
+ # reset hard all our local branches
+ local_public_branches = git.branches.remote.find_all do |b|
+ next if b.name == 'HEAD'
+ b.remote.name == localcache_remote_name
+ end
+
+ local_public_branches.each {|b| checkout_b(git, b, :force => true)}
+ end
+
+ yield(git)
+ end
+ end
+
+ # I _really_ don't like putting this in this tool. I couldn't figure out a
+ # way to lay it down with puppet.
+ def puppet_post_receive_hook
+ <<-HOOK
+#!/bin/sh
+
+update_working_dir() {
+ GIT_DIR=`pwd`
+ GIT_WORK_TREE="/etc/puppet/modules/main/`/bin/basename $GIT_DIR`"
+
+ pushd $GIT_WORK_TREE
+ git --git-dir=$GIT_DIR reset --hard $1
+ echo "$GIT_WORK_TREE updated."
+ popd
+}
+
+while read oldrev newrev ref; do
+ # We only care when master gets updated
+ if [[ $ref == 'refs/heads/master' ]]
+ then
+ update_working_dir $newrev
+ fi
+done
+ HOOK
+ end
+}
diff --git a/genome-sync/extra/genome-sync.spec b/genome-sync/extra/genome-sync.spec
new file mode 100644
index 0000000..300f030
--- /dev/null
+++ b/genome-sync/extra/genome-sync.spec
@@ -0,0 +1,54 @@
+%define ruby_sitelib %(ruby -rrbconfig -e "puts Config::CONFIG['sitelibdir']")
+%define gemdir %(ruby -rubygems -e 'puts Gem::dir' 2>/dev/null)
+%define gemname genome-sync
+%define geminstdir %{gemdir}/gems/%{gemname}-%{version}
+
+Summary: A tool for syncronizing Genome Repositories
+Name: rubygem-%{gemname}
+Version: 1.0.0
+Release: 5%{?dist}
+Group: Development/Languages
+License: GPLv2
+URL: https://fedorahosting.org/genome
+Source0: %{gemname}-%{version}.gem
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
+Requires: rubygems
+Requires: rubygem(git) >= 1.0.7.1
+Requires: rubygem(main)
+Requires: rubygem(highline)
+BuildRequires: rubygems
+BuildArch: noarch
+Provides: rubygem(%{gemname}) = %{version}
+
+%description
+A tool for syncronizing Genome Repositories
+
+
+%prep
+
+%build
+
+%install
+rm -rf %{buildroot}
+mkdir -p %{buildroot}%{gemdir}
+gem install --local --install-dir %{buildroot}%{gemdir} \
+ --force %{SOURCE0}
+mkdir -p %{buildroot}/%{_bindir}
+mv %{buildroot}%{gemdir}/bin/* %{buildroot}/%{_bindir}
+rmdir %{buildroot}%{gemdir}/bin
+find %{buildroot}%{geminstdir}/bin -type f | xargs chmod a+x
+
+%clean
+rm -rf %{buildroot}
+
+%files
+%defattr(-, root, root, -)
+%{_bindir}/genome-sync
+%{gemdir}/gems/%{gemname}-%{version}/
+%{gemdir}/cache/%{gemname}-%{version}.gem
+%{gemdir}/specifications/%{gemname}-%{version}.gemspec
+
+
+%changelog
+* Tue Jun 24 2008 <bleanhar@redhat.com> - 1.0.0-1
+- Initial package
diff --git a/genome-sync/lib/genome-sync.rb b/genome-sync/lib/genome-sync.rb
new file mode 100644
index 0000000..f4c370c
--- /dev/null
+++ b/genome-sync/lib/genome-sync.rb
@@ -0,0 +1,43 @@
+# Copyright (C) 2008 Red Hat, Inc
+
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# a long with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+# Copyright (C) 2008 Red Hat, Inc
+
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# a long with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module GenomeSync
+ AppName = "genome-sync"
+
+ module Version
+ MAJOR = 1
+ MINOR = 0
+ BUILD = 0
+
+ STRING = [MAJOR, MINOR, BUILD].join(".")
+ end
+end