1
0
Fork 0
mirror of https://github.com/capistrano/capistrano synced 2023-03-27 23:21:18 -04:00

Merge pull request #828 from coffeeaddict/decapsulated-scm

Decapsulated scm
This commit is contained in:
Tom Clements 2013-12-20 05:57:58 -08:00
commit f4d2bfe358
9 changed files with 446 additions and 10 deletions

View file

@ -5,6 +5,7 @@ Reverse Chronological Order:
## master ## master
* Add a command line option to control role filter (`--roles`) (@andytinycat) * Add a command line option to control role filter (`--roles`) (@andytinycat)
* Use an SCM object with a pluggable strategy
## `3.1.0` (not released) ## `3.1.0` (not released)

View file

@ -1 +1,36 @@
load File.expand_path("../tasks/git.rake", __FILE__) load File.expand_path("../tasks/git.rake", __FILE__)
require 'capistrano/scm'
class Capistrano::Git < Capistrano::SCM
# execute git with argument in the context
#
def git(*args)
args.unshift :git
context.execute *args
end
# The Capistrano default strategy for git. You should want to use this.
module DefaultStrategy
def test
test! " [ -f #{repo_path}/HEAD ] "
end
def check
test! :git, :'ls-remote', repo_url
end
def clone
git :clone, '--mirror', repo_url, repo_path
end
def update
git :remote, :update
end
def release
git :archive, fetch(:branch), '| tar -x -C', release_path
end
end
end

View file

@ -1 +1,33 @@
load File.expand_path("../tasks/hg.rake", __FILE__) load File.expand_path("../tasks/hg.rake", __FILE__)
require 'capistrano/scm'
class Capistrano::Hg < Capistrano::SCM
# execute hg in context with arguments
def hg(*args)
args.unshift(:hg)
context.execute *args
end
module DefaultStrategy
def test
test! " [ -d #{repo_path}/.hg ] "
end
def check
hg "id", repo_url
end
def clone
hg "clone", "--noupdate", repo_url, repo_path
end
def update
hg "pull"
end
def release
hg "archive", release_path, "--rev", fetch(:branch)
end
end
end

116
lib/capistrano/scm.rb Normal file
View file

@ -0,0 +1,116 @@
module Capistrano
# Base class for SCM strategy providers.
#
# @abstract
#
# @attr_reader [Rake] context
#
# @author Hartog de Mik
#
class SCM
attr_reader :context
# Provide a wrapper for the SCM that loads a strategy for the user.
#
# @param [Rake] context The context in which the strategy should run
# @param [Module] strategy A module to include into the SCM instance. The
# module should provide the abstract methods of Capistrano::SCM
#
def initialize(context, strategy)
@context = context
singleton = class << self; self; end
singleton.send(:include, strategy)
end
# Call test in context
def test!(*args)
context.test *args
end
# The repository URL accoriding to the context
def repo_url
context.repo_url
end
# The repository path accoriding to the context
def repo_path
context.repo_path
end
# The release path accoriding to the context
def release_path
context.release_path
end
# Fetch a var from the context
# @param [Symbol] variable The variable to fetch
# @param [Object] default The default value if not found
#
def fetch(*args)
context.fetch(*args)
end
# @abstract
#
# Your implementation should check the existance of a cache repository on
# the deployment target
#
# @return [Boolean]
#
def test
raise NotImplementedError.new(
"Your SCM strategy module should provide a #test method"
)
end
# @abstract
#
# Your implementation should check if the specified remote-repository is
# available.
#
# @return [Boolean]
#
def check
raise NotImplementedError.new(
"Your SCM strategy module should provide a #check method"
)
end
# @abstract
#
# Create a (new) clone of the remote-repository on the deployment target
#
# @return void
#
def clone
raise NotImplementedError.new(
"Your SCM strategy module should provide a #clone method"
)
end
# @abstract
#
# Update the clone on the deployment target
#
# @return void
#
def update
raise NotImplementedError.new(
"Your SCM strategy module should provide a #update method"
)
end
# @abstract
#
# Copy the contents of the cache-repository onto the release path
#
# @return void
#
def release
raise NotImplementedError.new(
"Your SCM strategy module should provide a #release method"
)
end
end
end

View file

@ -1,5 +1,9 @@
namespace :git do namespace :git do
def strategy
@strategy ||= Capistrano::Git.new(self, fetch(:git_strategy, Capistrano::Git::DefaultStrategy))
end
set :git_environmental_variables, ->() { set :git_environmental_variables, ->() {
{ {
git_askpass: "/bin/echo", git_askpass: "/bin/echo",
@ -21,7 +25,7 @@ namespace :git do
fetch(:branch) fetch(:branch)
on release_roles :all do on release_roles :all do
with fetch(:git_environmental_variables) do with fetch(:git_environmental_variables) do
exit 1 unless test :git, :'ls-remote', repo_url exit 1 unless strategy.check
end end
end end
end end
@ -29,12 +33,12 @@ namespace :git do
desc 'Clone the repo to the cache' desc 'Clone the repo to the cache'
task clone: :'git:wrapper' do task clone: :'git:wrapper' do
on release_roles :all do on release_roles :all do
if test " [ -f #{repo_path}/HEAD ] " if strategy.test
info t(:mirror_exists, at: repo_path) info t(:mirror_exists, at: repo_path)
else else
within deploy_path do within deploy_path do
with fetch(:git_environmental_variables) do with fetch(:git_environmental_variables) do
execute :git, :clone, '--mirror', repo_url, repo_path strategy.clone
end end
end end
end end
@ -46,7 +50,7 @@ namespace :git do
on release_roles :all do on release_roles :all do
within repo_path do within repo_path do
capturing_revisions do capturing_revisions do
execute :git, :remote, :update strategy.update
end end
end end
end end
@ -58,7 +62,7 @@ namespace :git do
with fetch(:git_environmental_variables) do with fetch(:git_environmental_variables) do
within repo_path do within repo_path do
execute :mkdir, '-p', release_path execute :mkdir, '-p', release_path
execute :git, :archive, fetch(:branch), '| tar -x -C', release_path strategy.release
end end
end end
end end

View file

@ -1,19 +1,23 @@
namespace :hg do namespace :hg do
def strategy
@strategy ||= Capistrano::Hg.new(self, fetch(:hg_strategy, Capistrano::Hg::DefaultStrategy))
end
desc 'Check that the repo is reachable' desc 'Check that the repo is reachable'
task :check do task :check do
on release_roles :all do on release_roles :all do
execute "hg", "id", repo_url strategy.check
end end
end end
desc 'Clone the repo to the cache' desc 'Clone the repo to the cache'
task :clone do task :clone do
on release_roles :all do on release_roles :all do
if test " [ -d #{repo_path}/.hg ] " if strategy.test
info t(:mirror_exists, at: repo_path) info t(:mirror_exists, at: repo_path)
else else
within deploy_path do within deploy_path do
execute "hg", "clone", "--noupdate", repo_url, repo_path strategy.clone
end end
end end
end end
@ -23,7 +27,7 @@ namespace :hg do
task :update => :'hg:clone' do task :update => :'hg:clone' do
on release_roles :all do on release_roles :all do
within repo_path do within repo_path do
execute "hg", "pull" strategy.update
end end
end end
end end
@ -32,7 +36,7 @@ namespace :hg do
task :create_release => :'hg:update' do task :create_release => :'hg:update' do
on release_roles :all do on release_roles :all do
within repo_path do within repo_path do
execute "hg", "archive", release_path, "--rev", fetch(:branch) strategy.release
end end
end end
end end

View file

@ -0,0 +1,70 @@
require 'spec_helper'
require 'capistrano/git'
module Capistrano
describe Git do
let(:context) { Class.new.new }
subject { Capistrano::Git.new(context, Capistrano::Git::DefaultStrategy) }
describe "#git" do
it "should call execute git in the context, with arguments" do
context.expects(:execute).with(:git, :init)
subject.git(:init)
end
end
end
describe Git::DefaultStrategy do
let(:context) { Class.new.new }
subject { Capistrano::Git.new(context, Capistrano::Git::DefaultStrategy) }
describe "#test" do
it "should call test for repo HEAD" do
context.expects(:repo_path).returns("/path/to/repo")
context.expects(:test).with " [ -f /path/to/repo/HEAD ] "
subject.test
end
end
describe "#check" do
it "should test the repo url" do
context.expects(:repo_url).returns(:url)
context.expects(:test).with(:git, :'ls-remote', :url).returns(true)
subject.check
end
end
describe "#clone" do
it "should run git clone" do
context.expects(:repo_url).returns(:url)
context.expects(:repo_path).returns(:path)
context.expects(:execute).with(:git, :clone, '--mirror', :url, :path)
subject.clone
end
end
describe "#update" do
it "should run git update" do
context.expects(:execute).with(:git, :remote, :update)
subject.update
end
end
describe "#release" do
it "should run git archive" do
context.expects(:fetch).returns(:branch)
context.expects(:release_path).returns(:path)
context.expects(:execute).with(:git, :archive, :branch, '| tar -x -C', :path)
subject.release
end
end
end
end

View file

@ -0,0 +1,70 @@
require 'spec_helper'
require 'capistrano/hg'
module Capistrano
describe Hg do
let(:context) { Class.new.new }
subject { Capistrano::Hg.new(context, Capistrano::Hg::DefaultStrategy) }
describe "#hg" do
it "should call execute hg in the context, with arguments" do
context.expects(:execute).with(:hg, :init)
subject.hg(:init)
end
end
end
describe Hg::DefaultStrategy do
let(:context) { Class.new.new }
subject { Capistrano::Hg.new(context, Capistrano::Hg::DefaultStrategy) }
describe "#test" do
it "should call test for repo HEAD" do
context.expects(:repo_path).returns("/path/to/repo")
context.expects(:test).with " [ -d /path/to/repo/.hg ] "
subject.test
end
end
describe "#check" do
it "should test the repo url" do
context.expects(:repo_url).returns(:url)
context.expects(:execute).with(:hg, "id", :url)
subject.check
end
end
describe "#clone" do
it "should run hg clone" do
context.expects(:repo_url).returns(:url)
context.expects(:repo_path).returns(:path)
context.expects(:execute).with(:hg, "clone", '--noupdate', :url, :path)
subject.clone
end
end
describe "#update" do
it "should run hg update" do
context.expects(:execute).with(:hg, "pull")
subject.update
end
end
describe "#release" do
it "should run hg archive" do
context.expects(:fetch).returns(:branch)
context.expects(:release_path).returns(:path)
context.expects(:execute).with(:hg, "archive", :path, "--rev", :branch)
subject.release
end
end
end
end

View file

@ -0,0 +1,104 @@
require 'spec_helper'
require 'capistrano/scm'
module RaiseNotImplementedMacro
def raise_not_implemented_on(method)
it "should raise NotImplemented on #{method}" do
expect {
subject.send(method)
}.to raise_error(NotImplementedError)
end
end
end
RSpec.configure do
include RaiseNotImplementedMacro
end
module DummyStrategy
def test
test!("you dummy!")
end
end
module BlindStrategy; end
module Capistrano
describe SCM do
let(:context) { Class.new.new }
describe "#initialize" do
subject { Capistrano::SCM.new(context, DummyStrategy) }
it "should load the provided strategy" do
context.expects(:test).with("you dummy!")
subject.test
end
end
describe "Convenience methods" do
subject { Capistrano::SCM.new(context, BlindStrategy) }
describe "#test!" do
it "should return call test on the context" do
context.expects(:test).with(:x)
subject.test!(:x)
end
end
describe "#repo_url" do
it "should return the repo url according to the context" do
context.expects(:repo_url).returns(:url)
subject.repo_url.should == :url
end
end
describe "#repo_path" do
it "should return the repo path according to the context" do
context.expects(:repo_path).returns(:path)
subject.repo_path.should == :path
end
end
describe "#release_path" do
it "should return the release path according to the context" do
context.expects(:release_path).returns('/path/to/nowhere')
subject.release_path.should == '/path/to/nowhere'
end
end
describe "#fetch" do
it "should call fetch on the context" do
context.expects(:fetch)
subject.fetch(:branch)
end
end
end
describe "With a 'blind' strategy" do
subject { Capistrano::SCM.new(context, BlindStrategy) }
describe "#test" do
raise_not_implemented_on(:test)
end
describe "#check" do
raise_not_implemented_on(:check)
end
describe "#clone" do
raise_not_implemented_on(:clone)
end
describe "#update" do
raise_not_implemented_on(:update)
end
describe "#release" do
raise_not_implemented_on(:release)
end
end
end
end