lib/rscm/base.rb in rscm-0.3.16 vs lib/rscm/base.rb in rscm-0.4.0
- old
+ new
@@ -1,116 +1,87 @@
require 'fileutils'
require 'rscm/revision'
require 'rscm/path_converter'
module RSCM
- # This class defines the RSCM API. The documentation of the various methods
- # uses CVS and Subversion's terminology. (For example, checkout means 'get working copy',
- # not 'lock for private edit' as in ClearCase or VSS terminology).
+ # This class defines the RSCM API, which offers access to an SCM working copy
+ # as well as a 'central' repository.
#
- # Concrete subclasses of this class provide an API to manage a local working copy
- # as well as an associated 'central' repository. The main responsibility is working
- # copy operations:
+ # Concrete subclasses of this class (concrete adapters) implement the integration
+ # with the respective SCMs.
#
- # * add
- # * checkout
- # * checked_out?
- # * diff
- # * edit
- # * ls
- # * move
- # * revisions
- # * uptodate?
- # * file
- # * destroy_working_copy
+ # Most of the methods take an optional +options+ Hash (named parameters), allowing
+ # the following options:
#
- # In addition to operations related to working copies, the same instance should provide
- # methods to administer the working copy's associated 'central' repository. These are:
+ # * <tt>:stdout</tt>: Path to file name where stdout of SCM operations are written.
+ # * <tt>:stdout</tt>: Path to file name where stderr of SCM operations are written.
#
- # * central_exists?
- # * create_central
- # * can_create_central?
- # * import_central
- # * install_trigger
- # * supports_trigger? / can_install_trigger?
- # * trigger_installed?
- # * trigger_mechanism
- # * uninstall_trigger
+ # In stead of specifying the +options+ parameters for every API method, it's possible
+ # to assign default options via the +default_options+ attribute.
#
- # Some methods are a bit fuzzy with respect to their relevance to the working copy or
- # the associated central repository, as it depends on the nature of the individual underlying
- # SCMs. These methods are:
- #
- # * checkout_command_line
- # * label
- # * name
- # * transactional? / atomic?
- # * update_command_line
- #
# Some of the methods in this API use +from_identifier+ and +to_identifier+.
# These identifiers can be either a UTC Time (according to the SCM's clock)
# or a String or Integer representing a label/revision
# (according to the SCM's native label/revision scheme).
#
# If +from_identifier+ or +to_identifier+ are +nil+ they should respectively default to
# Time.epoch or Time.infinite.
#
- # TODO: rename this superclass to 'Base'
- #
class Base
-# TODO: Make revisions yield revisions as they are determined, to avoid
-# having to load them all into memory before the method exits. Careful not to
-# use yielded revisions to do another scm hit - like get diffs. Some SCMs
-# might dead lock on this. Implement a guard for that.
-# TODO: Add some visitor support here too?
-
- public
+ attr_accessor :default_options
+ # Transforms +raw_identifier+ into the native rype used for revisions.
+ def to_identifier(raw_identifier)
+ raw_identifier.to_s
+ end
+
+ # Sets the checkout dir (working copy). Should be set prior to most other method
+ # invocations (depending on the implementation).
def checkout_dir=(dir)
@checkout_dir = PathConverter.filepath_to_nativepath(dir, false)
end
+ # Gets the working copy directory.
def checkout_dir
@checkout_dir
end
- def to_yaml_properties
+ def to_yaml_properties #:nodoc:
props = instance_variables
props.delete("@checkout_dir")
+ props.delete("@default_options")
props.sort!
end
# Destroys the working copy
def destroy_working_copy
FileUtils.rm_rf(checkout_dir) unless checkout_dir.nil?
end
- # Whether the physical SCM represented by this instance exists.
- #
+ # Whether or not the SCM represented by this instance exists.
def central_exists?
# The default implementation assumes yes - override if it can be
# determined programmatically.
true
end
# Whether or not this SCM is transactional (atomic).
- #
def transactional?
false
end
alias :atomic? :transactional?
# Creates a new 'central' repository. This is intended only for creation of 'central'
# repositories (not for working copies). You shouldn't have to call this method if a central repository
# already exists. This method is used primarily for testing of RSCM, but can also
- # be used if you *really* want to create a central repository.
+ # be used if you *really* want to use RSCM to create a central repository.
#
# This method should throw an exception if the repository cannot be created (for
# example if the repository is 'remote' or if it already exists).
#
- def create_central
+ def create_central(options={})
raise NotImplementedError
end
# Destroys the central repository. Shuts down any server processes and deletes the repository.
# WARNING: calling this may result in loss of data. Only call this if you really want to wipe
@@ -123,32 +94,33 @@
def can_create_central?
false
end
# Adds +relative_filename+ to the working copy.
- def add(relative_filename)
+ def add(relative_filename, options={})
raise NotImplementedError
end
# Schedules a move of +relative_src+ to +relative_dest+
# Should not take effect in the central repository until
# +commit+ is invoked.
- def move(relative_src, relative_dest)
+ def move(relative_src, relative_dest, options={})
raise NotImplementedError
end
- # Recursively imports files from a +dir+ into the central scm
- def import_central(dir, message)
- raise "Not implemented"
+ # Recursively imports files from <tt>:dir</tt> into the central scm,
+ # using commit message <tt>:message</tt>
+ def import_central(options)
+ raise NotImplementedError
end
# Open a file for edit - required by scms that check out files in read-only mode e.g. perforce
- def edit(file)
+ def edit(file, options={})
end
# Commit (check in) modified files.
- def commit(message)
+ def commit(message, options={})
raise NotImplementedError
end
# Checks out or updates contents from a central SCM to +checkout_dir+ - a local working copy.
# If this is a distributed SCM, this method should create a 'working copy' repository
@@ -164,16 +136,18 @@
#
# This method should be overridden for SCMs that are able to yield checkouts as they happen.
# For some SCMs this is not possible, or at least very hard. In that case, just override
# the checkout_silent method instead of this method (should be protected).
#
- def checkout(to_identifier=Time.infinity) # :yield: file
+ def checkout(to_identifier=Time.infinity, options={}) # :yield: file
+ to_identifier=Time.infinity if to_identifier.nil?
+
# the OS doesn't store file timestamps with fractions.
before_checkout_time = Time.now.utc - 1
# We expect subclasses to implement this as a protected method (unless this whole method is overridden).
- checkout_silent(to_identifier)
+ checkout_silent(to_identifier, options)
files = Dir["#{@checkout_dir}/**/*"]
added = []
files.each do |file|
added << file if File.mtime(file).utc > before_checkout_time
end
@@ -182,21 +156,18 @@
end
added_file_paths = added.find_all do |path|
File.file?(path)
end
relative_added_file_paths = to_relative(checkout_dir, added_file_paths)
- relative_added_file_paths.each do |path|
- yield path if block_given?
- end
relative_added_file_paths
end
# Returns a Revisions object for the period specified by +from_identifier+ (exclusive, i.e. after)
# and +to_identifier+ (inclusive). If +relative_path+ is specified, the result will only contain
# revisions pertaining to that path.
#
- def revisions(from_identifier, to_identifier=Time.infinity, relative_path=nil)
+ def revisions(from_identifier, options={})
raise NotImplementedError
end
# Returns the HistoricFile representing the root of the repo
def rootdir
@@ -206,30 +177,21 @@
# Returns a HistoricFile for +relative_path+
def file(relative_path, dir)
HistoricFile.new(relative_path, dir, self)
end
- # Returns an Array of the children under +relative_path+
- def ls(relative_path)
- raise NotImplementedError
- end
-
# Opens a revision_file
def open(revision_file, &block) #:yield: io
raise NotImplementedError
end
# Whether the working copy is in synch with the central
# repository's revision/time identified by +identifier+.
# If +identifier+ is nil, 'HEAD' of repository should be assumed.
#
- # TODO: rename to in_synch?
def uptodate?(identifier)
- # Suboptimal algorithm that works for all SCMs.
- # Subclasses can override this to improve efficiency.
-
- revisions(identifier).empty?
+ raise NotImplementedError
end
# Whether the project is checked out from the central repository or not.
# Subclasses should override this to check for SCM-specific administrative
# files if appliccable
@@ -298,9 +260,22 @@
end
true
end
protected
+
+ # Wrapper for CommandLine.execute that provides default values for
+ # dir plus any options set in default_options (typically stdout and stderr).
+ def execute(cmd, options={}, &proc)
+ default_dir = @checkout_dir.nil? ? Dir.pwd : @checkout_dir
+ options = {:dir => default_dir}.merge(default_options).merge(options)
+ begin
+ CommandLine.execute(cmd, options, &proc)
+ rescue CommandLine::OptionError => e
+ e.message += "\nEither specify default_options on the scm object, or pass the required options to the method"
+ raise e
+ end
+ end
# Takes an array of +absolute_paths+ and turns it into an array
# of paths relative to +dir+
#
def to_relative(dir, absolute_paths)