Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added new SCM Syncers: SVN and Git

  • Loading branch information...
commit ba62e989a2168abfd5c7ef0fb4e57eb3e28d46dc 1 parent 1d923a9
@kikito kikito authored
View
1  README.md
@@ -88,6 +88,7 @@ Below you find a list of components that Backup currently supports. If you'd lik
- RSync (Push, Pull and Local)
- Amazon S3
- Rackspce Cloud Files
+- SCM support (SVN and Git)
[Syncer Wiki Page](https://github.com/meskyanichi/backup/wiki/Syncers)
View
18 lib/backup.rb
@@ -72,6 +72,11 @@ module RSync
autoload :Push, File.join(SYNCER_PATH, 'rsync', 'push')
autoload :Pull, File.join(SYNCER_PATH, 'rsync', 'pull')
end
+ module SCM
+ autoload :Base, File.join(SYNCER_PATH, 'scm', 'base')
+ autoload :Git, File.join(SYNCER_PATH, 'scm', 'git')
+ autoload :SVN, File.join(SYNCER_PATH, 'scm', 'svn')
+ end
end
##
@@ -164,11 +169,16 @@ module Syncer
autoload :Cloud, File.join(CONFIGURATION_PATH, 'syncer', 'cloud')
autoload :CloudFiles, File.join(CONFIGURATION_PATH, 'syncer', 'cloud_files')
autoload :S3, File.join(CONFIGURATION_PATH, 'syncer', 's3')
+ module SCM
+ autoload :Base, File.join(CONFIGURATION_PATH, 'syncer', 'scm', 'base')
+ autoload :Git, File.join(CONFIGURATION_PATH, 'syncer', 'scm', 'git')
+ autoload :SVN, File.join(CONFIGURATION_PATH, 'syncer', 'scm', 'svn')
+ end
module RSync
- autoload :Base, File.join(CONFIGURATION_PATH, 'syncer', 'rsync', 'base')
- autoload :Local, File.join(CONFIGURATION_PATH, 'syncer', 'rsync', 'local')
- autoload :Push, File.join(CONFIGURATION_PATH, 'syncer', 'rsync', 'push')
- autoload :Pull, File.join(CONFIGURATION_PATH, 'syncer', 'rsync', 'pull')
+ autoload :Base, File.join(CONFIGURATION_PATH, 'syncer', 'rsync', 'base')
+ autoload :Local, File.join(CONFIGURATION_PATH, 'syncer', 'rsync', 'local')
+ autoload :Push, File.join(CONFIGURATION_PATH, 'syncer', 'rsync', 'push')
+ autoload :Pull, File.join(CONFIGURATION_PATH, 'syncer', 'rsync', 'pull')
end
end
View
6 lib/backup/config.rb
@@ -111,7 +111,11 @@ def add_dsl_constants!
# Encryptors
['OpenSSL', 'GPG'],
# Syncers
- ['Rackspace', 'S3', { 'RSync' => ['Push', 'Pull', 'Local'] }],
+ [ 'Rackspace',
+ 'S3',
+ { 'SCM' => ['SVN', 'Git'] },
+ { 'RSync' => ['Push', 'Pull', 'Local'] }
+ ],
# Notifiers
['Mail', 'Twitter', 'Campfire', 'Presently', 'Prowl', 'Hipchat']
]
View
17 lib/backup/configuration/syncer/scm/base.rb
@@ -0,0 +1,17 @@
+# encoding: utf-8
+
+module Backup
+ module Configuration
+ module Syncer
+ module SCM
+ class Base < Syncer::Base
+ class << self
+
+ attr_accessor :protocol, :username, :password, :ip, :port, :path, :additional_options
+
+ end
+ end
+ end
+ end
+ end
+end
View
12 lib/backup/configuration/syncer/scm/git.rb
@@ -0,0 +1,12 @@
+# encoding: utf-8
+
+module Backup
+ module Configuration
+ module Syncer
+ module SCM
+ class Git < Base
+ end
+ end
+ end
+ end
+end
View
12 lib/backup/configuration/syncer/scm/svn.rb
@@ -0,0 +1,12 @@
+# encoding: utf-8
+
+module Backup
+ module Configuration
+ module Syncer
+ module SCM
+ class SVN < Base
+ end
+ end
+ end
+ end
+end
View
2  lib/backup/model.rb
@@ -128,7 +128,7 @@ def sync_with(name, &block)
# Warn user of DSL change from 'RSync' to 'RSync::Local'
if name.to_s == 'Backup::Config::RSync'
Logger.warn Errors::ConfigError.new(<<-EOS)
- Configuration Update Needed for Syncer::RSync
+ Config Update Needed for Syncer::RSync
The RSync Syncer has been split into three separate modules:
RSync::Local, RSync::Push and RSync::Pull
Please update your configuration for your local RSync Syncer
View
80 lib/backup/syncer/scm/base.rb
@@ -0,0 +1,80 @@
+# encoding: utf-8
+
+module Backup
+ module Syncer
+ module SCM
+ class Base < Syncer::Base
+
+ ##
+ # In a server url such as:
+ # https://jimmy:password@example.com:80
+ # -> [protocol]://[username]:[password][ip]:[port]
+ attr_accessor :protocol, :username, :password, :ip, :port
+
+ ##
+ # repositories is an alias to directories; provided just for clarity
+ alias :repositories :directories
+ alias :repositories= :directories=
+
+ ##
+ # Instantiates a new Repository Syncer object
+ # and sets the default configuration
+ def initialize
+ load_defaults!
+
+ @path ||= 'backups'
+ @directories = Array.new
+ end
+
+ def perform!
+ Logger.message("#{ self.class } started syncing '#{ path }'.")
+ repositories.each do |repository|
+ backup_repository! repository
+ end
+ end
+
+ def authority
+ "#{protocol}://#{credentials}#{ip}#{prefixed_port}"
+ end
+
+ def repository_urls
+ repositories.collect{ |r| repository_url(r) }
+ end
+
+ def repository_url(repository)
+ "#{authority}#{repository}"
+ end
+
+ def repository_local_path(repository)
+ File.join(path, repository)
+ end
+
+ def repository_absolute_local_path(repository)
+ File.absolute_path(repository_local_path(repository))
+ end
+
+ def repository_local_container_path(repository)
+ File.dirname(repository_local_path(repository))
+ end
+
+ def create_repository_local_container!(repository)
+ FileUtils.mkdir_p(repository_local_container_path(repository))
+ end
+
+ private
+
+ def credentials
+ "#{username}#{prefixed_password}@" if username
+ end
+
+ def prefixed_password
+ ":#{password}" if password
+ end
+
+ def prefixed_port
+ ":#{port}" if port
+ end
+ end
+ end
+ end
+end
View
49 lib/backup/syncer/scm/git.rb
@@ -0,0 +1,49 @@
+# encoding: utf-8
+
+module Backup
+ module Syncer
+ module SCM
+ class Git < Base
+
+ attr_accessor :username, :password, :host, :protocol, :port, :repo_path, :path
+
+ def initialize(&block)
+ super
+
+ @protocol ||= 'git'
+
+ instance_eval(&block) if block_given?
+ end
+
+ def local_repository_exists?(repository)
+ local_path = repository_local_path(repository)
+ run "cd #{local_path} && git rev-parse --git-dir > /dev/null 2>&1"
+ return true
+ rescue Errors::CLI::SystemCallError
+ Logger.message("#{local_path} is not a repository")
+ return false
+ end
+
+ def clone_repository!(repository)
+ Logger.message("Cloning repository in '#{repository_local_path(repository)}'.")
+ create_repository_local_container!(repository)
+ run "cd #{repository_local_container_path(repository)} && git clone --bare #{repository_url(repository)}"
+ end
+
+ def update_repository!(repository)
+ local_path = repository_local_path(repository)
+ Logger.message("Updating repository in '#{local_path}'.")
+ run "cd #{local_path} && git fetch --all"
+ end
+
+ def backup_repository!(repository)
+ if local_repository_exists?(repository)
+ update_repository!(repository)
+ else
+ clone_repository!(repository)
+ end
+ end
+ end
+ end
+ end
+end
View
56 lib/backup/syncer/scm/svn.rb
@@ -0,0 +1,56 @@
+# encoding: utf-8
+
+module Backup
+ module Syncer
+ module SCM
+ class SVN < Base
+
+ def initialize(&block)
+
+ super
+
+ @protocol ||= "http"
+
+ instance_eval(&block) if block_given?
+ end
+
+ def local_repository_exists?(repository)
+ run "svnadmin verify #{repository_local_path(repository)}"
+ return true
+ rescue Errors::CLI::SystemCallError
+ return false
+ end
+
+ def initialize_repository!(repository)
+ local_path = repository_local_path(repository)
+ absolute_path = repository_absolute_local_path(repository)
+ url = repository_url(repository)
+ hook_path = File.join(local_path, 'hooks', 'pre-revprop-change')
+
+ Logger.message("Initializing empty svn repository in '#{local_path}'.")
+
+ create_repository_local_container!(repository)
+
+ run "svnadmin create '#{local_path}'"
+ run "echo '#!/bin/sh' > '#{hook_path}'"
+ run "chmod +x '#{hook_path}'"
+ run "svnsync init file://#{absolute_path} #{url}"
+ end
+
+ def update_repository!(repository)
+ absolute_path = repository_absolute_local_path(repository)
+ local_path = repository_local_path(repository)
+
+ Logger.message("Updating svn repository in '#{local_path}'.")
+ run("svnsync sync file://#{absolute_path} --non-interactive")
+ end

Why svnsync instead of svnadmin hotcopy? I don't know the semantics of the former, but the latter allows the repository to be active during the backup; that is, it won't lose transactions in-flight. But, it's been a long time since I looked at backing up svn repositories; I'm probably out of date.

@kikito Collaborator
kikito added a note

I'm not certain why we used svnsync, but I'm sure we knew svnadmin existed and yet we used svnsync. But it has been years since I have had to use these, I'm afraid I have forgotten the details. I'd venture it has to do with the permissions required for each action. But that's a guess.

I've skimmed some docs; maybe svnsync because it can operate remotely, whereas svnadmin hotcopy needs to operate locally? Flexibility I guess...? Except there are file:// URLs there, note remote ones. Shrug.

@kikito Collaborator
kikito added a note
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+
+ def backup_repository!(repository)
+ initialize_repository!(repository) unless local_repository_exists?(repository)
+ update_repository!(repository)
+ end
+ end
+ end
+ end
+end
View
2  spec/cli/utility_spec.rb
@@ -188,7 +188,7 @@
[--config-path=CONFIG_PATH] # Path to your Backup configuration directory
[--databases=DATABASES] # (mongodb, mysql, postgresql, redis, riak)
[--storages=STORAGES] # (cloud_files, dropbox, ftp, local, ninefold, rsync, s3, scp, sftp)
- [--syncers=SYNCERS] # (cloud_files, rsync_local, rsync_pull, rsync_push, s3)
+ [--syncers=SYNCERS] # (cloud_files, rsync_local, rsync_pull, rsync_push, s3, scm_git, scm_svn)
[--encryptors=ENCRYPTORS] # (gpg, openssl)
[--compressors=COMPRESSORS] # (bzip2, gzip, lzma, pbzip2)
[--notifiers=NOTIFIERS] # (campfire, hipchat, mail, presently, prowl, twitter)
View
48 spec/configuration/syncer/scm/base_spec.rb
@@ -0,0 +1,48 @@
+# encoding: utf-8
+
+require File.expand_path('../../../../spec_helper.rb', __FILE__)
+
+describe Backup::Configuration::Syncer::SCM do
+ before do
+ Backup::Configuration::Syncer::SCM::Base.defaults do |default|
+ default.protocol = 'http'
+ default.username = 'my_user_name'
+ default.password = 'secret'
+ default.ip = 'example.com'
+ default.port = 1234
+ default.path = '~/backups/'
+ default.additional_options = 'some_additional_options'
+ # base.directories/repositories # can not have a default value
+
+ end
+ end
+
+ after { Backup::Configuration::Syncer::SCM::Base.clear_defaults! }
+
+ it 'should set the default base configuration' do
+ base = Backup::Configuration::Syncer::SCM::Base
+ base.protocol.should == 'http'
+ base.username.should == 'my_user_name'
+ base.password.should == 'secret'
+ base.ip.should == 'example.com'
+ base.port.should == 1234
+ base.path.should == '~/backups/'
+ base.additional_options == 'some_additional_options'
+ end
+
+ describe '#clear_defaults!' do
+ it 'should clear all the defaults, resetting them to nil' do
+ Backup::Configuration::Syncer::SCM::Base.clear_defaults!
+
+ base = Backup::Configuration::Syncer::SCM::Base
+ base.protocol.should == nil
+ base.username.should == nil
+ base.password.should == nil
+ base.ip.should == nil
+ base.port.should == nil
+ base.path.should == nil
+ base.additional_options == nil
+ end
+ end
+
+end
View
10 spec/configuration/syncer/scm/git_spec.rb
@@ -0,0 +1,10 @@
+# encoding: utf-8
+
+require File.expand_path('../../../../spec_helper.rb', __FILE__)
+
+describe Backup::Configuration::Syncer::SCM::Git do
+ it 'should be a subclass of SCM::Base' do
+ git = Backup::Configuration::Syncer::SCM::Git
+ git.superclass.should == Backup::Configuration::Syncer::SCM::Base
+ end
+end
View
10 spec/configuration/syncer/scm/svn_spec.rb
@@ -0,0 +1,10 @@
+# encoding: utf-8
+
+require File.expand_path('../../../../spec_helper.rb', __FILE__)
+
+describe Backup::Configuration::Syncer::SCM::SVN do
+ it 'should be a subclass of SCM::Base' do
+ svn = Backup::Configuration::Syncer::SCM::SVN
+ svn.superclass.should == Backup::Configuration::Syncer::SCM::Base
+ end
+end
View
82 spec/storage/local_spec.rb.orig
@@ -0,0 +1,82 @@
+# encoding: utf-8
+
+require File.dirname(__FILE__) + '/../spec_helper'
+
+describe Backup::Storage::Local do
+
+ let(:local) do
+ Backup::Storage::Local.new do |local|
+ local.path = '~/backups/'
+ local.keep = 20
+ end
+ end
+
+ before do
+ Backup::Configuration::Storage::Local.clear_defaults!
+ end
+
+ it 'should have defined the configuration properly' do
+ local.path.should == "#{ENV['HOME']}/backups/"
+ local.keep.should == 20
+ end
+
+ it 'should use the defaults if a particular attribute has not been defined' do
+ Backup::Configuration::Storage::Local.defaults do |local|
+ local.path = '~/backups'
+ end
+
+ local = Backup::Storage::Local.new do |local|
+ local.path = '~/my-backups'
+ end
+
+ local.path.should == "#{ENV['HOME']}/my-backups"
+ end
+
+ it 'should have its own defaults' do
+ local = Backup::Storage::Local.new
+ local.path.should == "#{ENV['HOME']}/backups"
+ end
+
+ describe '#transfer!' do
+ before do
+ local.stubs(:create_local_directories!)
+ end
+
+ it 'should transfer the provided file to the path' do
+ Backup::Model.new('blah', 'blah') {}
+
+ local.expects(:create_local_directories!)
+
+ FileUtils.expects(:cp).with(
+ File.join(Backup::TMP_PATH, "#{ Backup::TIME }.#{ Backup::TRIGGER }.tar"),
+ File.join("#{ENV['HOME']}/backups/myapp", "#{ Backup::TIME }.#{ Backup::TRIGGER }.tar")
+ )
+
+ local.send(:transfer!)
+ end
+ end
+
+ describe '#remove!' do
+ it 'should remove the file from the remote server path' do
+ FileUtils.expects(:rm).with("#{ENV['HOME']}/backups/myapp/#{ Backup::TIME }.#{ Backup::TRIGGER }.tar")
+ local.send(:remove!)
+ end
+ end
+
+ describe '#create_remote_directories!' do
+ it 'should properly create remote directories one by one' do
+ local.path = "#{ENV['HOME']}/backups/some_other_folder/another_folder"
+ FileUtils.expects(:mkdir_p).with("#{ENV['HOME']}/backups/some_other_folder/another_folder/myapp")
+ local.send(:create_local_directories!)
+ end
+ end
+
+ describe '#perform' do
+ it 'should invoke transfer! and cycle!' do
+ local.expects(:transfer!)
+ local.expects(:cycle!)
+ local.perform!
+ end
+ end
+
+end
View
174 spec/syncer/scm/base_spec.rb
@@ -0,0 +1,174 @@
+# encoding: utf-8
+
+require File.expand_path('../../../spec_helper.rb', __FILE__)
+
+describe Backup::Syncer::SCM::Base do
+ let(:syncer) { Backup::Syncer::SCM::Base.new }
+
+ describe '#initialize' do
+
+ it 'should use default values' do
+ syncer.path.should == 'backups'
+ syncer.repositories.should == []
+ end
+
+ context 'when setting configuration defaults' do
+ after { Backup::Configuration::Syncer::SCM::Base.clear_defaults! }
+
+ it 'should use the configured defaults' do
+ Backup::Configuration::Syncer::SCM::Base.defaults do |default|
+ default.path = 'some_path'
+ #default.directories = 'cannot_have_a_default_value'
+ end
+ syncer = Backup::Syncer::SCM::Base.new
+ syncer.path.should == 'some_path'
+ syncer.repositories.should == []
+ end
+ end
+
+ end # describe '#initialize'
+
+ describe '#repositories' do
+ before do
+ syncer.repositories = ['/some/repo.git', '/a/svn/repo']
+ end
+
+ context 'when no block is given' do
+ it 'should return repositories' do
+ syncer.repositories.should ==
+ ['/some/repo.git', '/a/svn/repo']
+ end
+ end
+
+ context 'when a block is given' do
+ it 'should evalute the block, allowing #add to add directories' do
+ syncer.repositories do
+ add '/a/new/repo.git'
+ add '/another/new/repo.git'
+ end
+ syncer.repositories.should == [
+ '/some/repo.git',
+ '/a/svn/repo',
+ '/a/new/repo.git',
+ '/another/new/repo.git'
+ ]
+ end
+ end
+ end # describe '#repositories'
+
+ describe '#add' do
+ before do
+ syncer.repositories = ['/some/repo.git', '/another/repo.git']
+ end
+
+ it 'should add the given path to repositories' do
+ syncer.add '/yet/another/repo.git'
+ syncer.repositories.should ==
+ ['/some/repo.git', '/another/repo.git', '/yet/another/repo.git']
+ end
+ end
+
+ context 'when handling fully-qualified repositories' do
+
+ before do
+ syncer.protocol = 'http'
+ syncer.username = 'jimmy'
+ syncer.password = 'secret'
+ syncer.ip = 'example.com'
+ syncer.port = '80'
+ end
+
+ describe '#authority' do
+
+ it "gets calculated using protocol, host, port and path" do
+ syncer.authority.should == "http://jimmy:secret@example.com:80"
+ end
+
+ it "ignores missing port successfully" do
+ syncer.port = nil
+ syncer.authority.should == "http://jimmy:secret@example.com"
+ end
+
+ it "ignores missing user successfully" do
+ syncer.username = nil
+ syncer.authority.should == "http://example.com:80"
+ end
+
+ it "ignores missing password successfully" do
+ syncer.password = nil
+ syncer.authority.should == "http://jimmy@example.com:80"
+ end
+ end
+
+ describe "#repository_url" do
+ it "given a repository, it returns the local path where it will be stored" do
+ syncer.repository_url('/my/repo.git').should == "http://jimmy:secret@example.com:80/my/repo.git"
+ end
+ end
+
+ describe "#repository_urls" do
+ it "returns the repositories prefixed with the authority" do
+ syncer.repositories do
+ add '/my/repo.git'
+ add '/my/other/repo.git'
+ end
+ syncer.repository_urls.should == [
+ "http://jimmy:secret@example.com:80/my/repo.git",
+ "http://jimmy:secret@example.com:80/my/other/repo.git"
+ ]
+ end
+ end
+ end
+
+ describe "#repository_local_path" do
+ it "returns the path of a given repository" do
+ syncer.repository_local_path("/my/repo.git").should == "backups/my/repo.git"
+ end
+ end
+
+ describe "#repository_absolute_local_path" do
+ it "returns the absolute path of a given repository" do
+ result = "/home/jimmy/backups/my/repo"
+ File.expects(:absolute_path).with("backups/my/repo").returns(result)
+ syncer.repository_absolute_local_path("/my/repo").should == result
+ end
+ end
+
+ describe "#repository_local_container_path" do
+ it "returns the path of the directory that will contain the repo locally" do
+ syncer.repository_local_container_path("/my/deep/repo.git").should == "backups/my/deep"
+ end
+ end
+
+ describe "#create_repository_local_container!" do
+ it "creates the container if needed" do
+ FileUtils.stubs(:mkdir_p)
+ FileUtils.expects(:mkdir_p).with("backups/my/deep")
+ syncer.create_repository_local_container!("/my/deep/repo.git")
+ end
+ end
+
+ describe "#perform!" do
+ it "invokes update_repository once per each repository" do
+ syncer.stubs(:backup_repository!)
+
+ syncer.repositories do
+ add '/my/repo.git'
+ add '/my/other/repo.git'
+ end
+
+ Backup::Logger.expects(:message).with("Backup::Syncer::SCM::Base started syncing 'backups'.")
+ syncer.expects(:backup_repository!).with('/my/repo.git')
+ syncer.expects(:backup_repository!).with('/my/other/repo.git')
+
+ syncer.perform!
+ end
+ end
+
+ describe "#backup_repository!" do
+ it "throws an error when invoked" do
+ lambda { syncer.backup_repository('my/repo.git') }.should raise_error
+ end
+ end
+
+end
View
80 spec/syncer/scm/git_spec.rb
@@ -0,0 +1,80 @@
+# encoding: utf-8
+require File.expand_path('../../../spec_helper.rb', __FILE__)
+
+describe Backup::Syncer::SCM::Git do
+
+ let(:git) do
+ Backup::Syncer::SCM::Git.new do |git|
+ git.ip = 'example.com'
+ git.repositories do
+ add '/a/repo.git'
+ add '/another/repo.git'
+ end
+ end
+ end
+
+ describe '#initialize' do
+ it 'should use default values' do
+ git.protocol == 'git'
+ git.path.should == 'backups'
+ git.repositories.should == ['/a/repo.git', '/another/repo.git']
+ end
+ end
+
+
+
+ it 'should be a subclass of SCM::Base' do
+ Backup::Syncer::SCM::Git.superclass.should == Backup::Syncer::SCM::Base
+ end
+
+ describe '#local_repository_exists?' do
+ let(:command) { "cd backups/my_repo && git rev-parse --git-dir > /dev/null 2>&1" }
+ it "returns false when not in a working copy" do
+ git.expects(:run).with(command).raises(Backup::Errors::CLI::SystemCallError)
+ git.local_repository_exists?("my_repo").should be_false
+ end
+
+ it "returns true when inside a working copy" do
+ git.expects(:run).with(command)
+ git.local_repository_exists?("my_repo").should be_true
+ end
+ end
+
+ describe '#clone_repository!' do
+ it 'initializes an empty repository' do
+ Backup::Logger.expects(:message).with("Cloning repository in 'backups/my/repo.git'.")
+ git.expects(:create_repository_local_container!).with('/my/repo.git')
+ git.expects(:run).with("cd backups/my && git clone --bare git://example.com/my/repo.git")
+
+ git.clone_repository!('/my/repo.git')
+ end
+ end
+
+ describe '#update_repository!' do
+ it 'invokes git fetch' do
+ Backup::Logger.expects(:message).with("Updating repository in 'backups/my/repo.git'.")
+ git.expects(:run).with("cd backups/my/repo.git && git fetch --all")
+
+ git.update_repository!('/my/repo.git')
+ end
+ end
+
+ describe '#backup_repository!' do
+ context 'when the local repository exists' do
+ it 'invokes update_repository!' do
+ git.expects(:local_repository_exists?).with('/my/repo.git').returns(true)
+ git.expects(:update_repository!).with('/my/repo.git')
+
+ git.backup_repository!('/my/repo.git')
+ end
+ end
+ context 'when the local repository does not exist' do
+ it 'invokes clone_repository!' do
+ git.expects(:local_repository_exists?).with('/my/repo.git').returns(false)
+ git.expects(:clone_repository!).with('/my/repo.git')
+
+ git.backup_repository!('/my/repo.git')
+ end
+ end
+ end
+end
View
94 spec/syncer/scm/svn_spec.rb
@@ -0,0 +1,94 @@
+# encoding: utf-8
+
+require File.expand_path('../../../spec_helper.rb', __FILE__)
+
+describe Backup::Syncer::SCM::SVN do
+
+ let(:svn) do
+ Backup::Syncer::SCM::SVN.new do |svn|
+ svn.ip = 'example.com'
+ svn.repositories do
+ add '/a/repo/trunk'
+ add '/another/repo/trunk'
+ end
+ end
+ end
+
+ describe '#initialize' do
+ it 'should use default values' do
+ svn.protocol == 'http'
+ svn.path.should == 'backups'
+ svn.repositories.should == ['/a/repo/trunk', '/another/repo/trunk']
+ end
+ end
+
+ it 'should be a subclass of SCM::Base' do
+ Backup::Syncer::SCM::SVN.superclass.should == Backup::Syncer::SCM::Base
+ end
+
+
+ describe '#local_repository_exists?' do
+ it "returns false when not in a working copy" do
+ svn.expects(:run).with('svnadmin verify backups/my_repo').raises(Backup::Errors::CLI::SystemCallError)
+ svn.local_repository_exists?("my_repo").should be_false
+ end
+ it "returns true when inside a working copy" do
+ svn.expects(:run).with('svnadmin verify backups/my_repo')
+ svn.local_repository_exists?("my_repo").should be_true
+ end
+ end
+
+ describe '#backup_repository!' do
+ context 'when the local repository exists' do
+ it 'invokes update_repository! only' do
+ svn.expects(:local_repository_exists?).with('/my/repo').returns(true)
+ svn.expects(:update_repository!).with('/my/repo')
+
+ svn.backup_repository!('/my/repo')
+ end
+ end
+ context 'when the local repository does not exist' do
+ it 'invokes initialize_repository! and then update_repository!' do
+
+ svn.expects(:local_repository_exists?).with('/my/repo').returns(false)
+ svn.expects(:initialize_repository!).with('/my/repo')
+ svn.expects(:update_repository!).with('/my/repo')
+
+ svn.backup_repository!('/my/repo')
+ end
+ end
+ end
+
+ describe '#initialize_repository!' do
+ it 'initializes an empty repository' do
+ absolute_path = '/home/jimmy/backups/my/repo'
+
+ Backup::Logger.expects(:message).with("Initializing empty svn repository in 'backups/my/repo'.")
+
+ svn.expects(:repository_absolute_local_path).with('/my/repo').returns(absolute_path)
+
+ svn.expects(:create_repository_local_container!).with('/my/repo')
+
+ svn.expects(:run).with("svnadmin create 'backups/my/repo'")
+ svn.expects(:run).with("echo '#!/bin/sh' > 'backups/my/repo/hooks/pre-revprop-change'")
+ svn.expects(:run).with("chmod +x 'backups/my/repo/hooks/pre-revprop-change'")
+ svn.expects(:run).with("svnsync init file://#{absolute_path} http://example.com/my/repo")
+
+ svn.initialize_repository!('/my/repo')
+ end
+ end
+
+ describe '#update_repository!' do
+ it 'updates an existing repository' do
+ absolute_path = '/home/jimmy/backups/my/repo'
+
+ Backup::Logger.expects(:message).with("Updating svn repository in 'backups/my/repo'.")
+
+ svn.expects(:repository_absolute_local_path).with('/my/repo').returns(absolute_path)
+ svn.expects(:run).with("svnsync sync file://#{absolute_path} --non-interactive")
+
+ svn.update_repository!('/my/repo')
+ end
+ end
+
+end
View
27 templates/cli/utility/syncer/scm_git
@@ -0,0 +1,27 @@
+ ##
+ # Git [Syncer]
+ #
+ # This mirrors a remote git repository
+ #
+ # Notes about usage:
+ #
+ # The backup must be performed on the server in which the final data is stored.
+ # In other words, this is not a PUSH-type backup, but a PULL one.
+ #
+ # Requirements:
+ #
+ # git must be accessible from the command line.
+ # The account performing the backup must be able to access the remote repository.
+
+ sync_with SCM::Git do |git|
+ git.protocol = "git" # Defaults to 'git'. Could also be "http" or "https"
+ git.port = 9418 # Optional. Could also be 80, 443, or a custom one
+ git.username = "peter" # optional
+ git.password = "secret" # optional
+ git.ip = "git.example.com"
+ git.path = "/home/backup/git" # Defaults to 'backups'. The path where the repos will be stored locally.
+ git.repositories do # absolute paths to repositories (git.example.com/project-1.git ...)
+ add '/project-1.git'
+ add '/another/project/repo.git'
+ end
+ end
View
31 templates/cli/utility/syncer/scm_svn
@@ -0,0 +1,31 @@
+ ##
+ # SVNSync [Syncer]
+ #
+ # This mirrors a remote svn repository using svnsync ( http://svn.apache.org/repos/asf/subversion/trunk/notes/svnsync.txt )
+ #
+ # The backup repositories are created using svnadmin.
+ #
+ # Notes about usage:
+ #
+ # As a result of how svsync works, the backup must be performed on the server in which the final backup is stored.
+ # In other words, this is not a PUSH-type backup, but a PULL one.
+ #
+ # Requirements:
+ #
+ # svn must be accessible from the command line.
+ # The account performing the backup must be able to access the remote svn repository.
+ # The account performing the backup must be able to perform svnadmin locally.
+ #
+
+ sync_with SCM::SVN do |svn|
+ svn.protocol = "http" # Defaults to 'http'. Could also be "http" or "svn"
+ svn.port = 80 # Optional. Could also be 443 (for https), 3690 (for svn) or a custom one
+ svn.username = "peter" # optional
+ svn.password = "secret" # optional
+ svn.ip = "svn.example.com"
+ svn.path = "/home/backup/svn" # Defaults to 'backups'. The path where the repos will be stored locally.
+ svn.repositories do # absolute paths to repositories (svn.example.com/project/trunk ...)
+ add '/project/trunk'
+ add '/another/project/repo'
+ end
+ end

0 comments on commit ba62e98

Please sign in to comment.
Something went wrong with that request. Please try again.