Class: RSCM::Base
- Inherits:
-
Object
- Object
- RSCM::Base
- Defined in:
- lib/rscm/base.rb,
lib/rscm/revision_poller.rb
Overview
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 (concrete adapters) implement the integration with the respective SCMs.
Most of the methods take an optional options
Hash (named parameters), allowing the following options:
-
:stdout
: Path to file name where stdout of SCM operations are written. -
:stdout
: Path to file name where stderr of SCM operations are written.
In stead of specifying the options
parameters for every API method, it’s possible to assign default options via the default_options
attribute.
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.
Constant Summary collapse
- TWO_WEEKS_AGO =
2*7*24*60*60
- THIRTY_TWO_WEEKS_AGO =
TWO_WEEKS_AGO * 16
- DEFAULT_QUIET_PERIOD =
Default time to wait for scm to be quiet (applies to non-transactional scms only)
15
Instance Attribute Summary collapse
-
#default_options ⇒ Object
Returns the value of attribute default_options.
-
#logger ⇒ Object
Returns the value of attribute logger.
Instance Method Summary collapse
- #==(other_scm) ⇒ Object
-
#add(relative_filename, options = {}) ⇒ Object
Adds
relative_filename
to the working copy. -
#can_create_central? ⇒ Boolean
Whether a repository can be created.
-
#central_exists?(options = {}) ⇒ Boolean
Whether or not the SCM represented by this instance exists.
-
#checked_out? ⇒ Boolean
Whether the project is checked out from the central repository or not.
-
#checkout(to_identifier = Time.infinity, options = {}) ⇒ Object
Checks out or updates contents from a central SCM to
checkout_dir
- a local working copy. -
#checkout_commandline(to_identifier = Time.infinity) ⇒ Object
The command line to run in order to check out a fresh working copy.
-
#checkout_dir ⇒ Object
Gets the working copy directory.
-
#checkout_dir=(dir) ⇒ Object
Sets the checkout dir (working copy).
-
#commit(message, options = {}) ⇒ Object
Commit (check in) modified files.
-
#create_central(options = {}) ⇒ Object
Creates a new ‘central’ repository.
-
#destroy_central ⇒ Object
Destroys the central repository.
-
#destroy_working_copy(options = {}) ⇒ Object
Destroys the working copy.
-
#diff(change, &block) ⇒ Object
Returns/yields an IO containing the unified diff of the change.
-
#edit(file, options = {}) ⇒ Object
Open a file for edit - required by scms that check out files in read-only mode e.g.
-
#file(relative_path, dir) ⇒ Object
Returns a HistoricFile for
relative_path
. -
#import_central(options) ⇒ Object
Recursively imports files from
:dir
into the central scm, using commit message:message
. -
#install_trigger(trigger_command, install_dir) ⇒ Object
Installs
trigger_command
in the SCM. -
#move(relative_src, relative_dest, options = {}) ⇒ Object
Schedules a move of
relative_src
torelative_dest
Should not take effect in the central repository untilcommit
is invoked. -
#open(revision_file, &block) ⇒ Object
Opens a revision_file.
-
#poll_new_revisions(options) ⇒ Object
Polls new revisions for since
last_revision
, or iflast_revision
is nil, polls since ‘now’ -options[:seconds_before_now]
. -
#revisions(from_identifier, options = {}) ⇒ Object
Returns a Revisions object for the period specified by
from_identifier
(exclusive, i.e. after) andto_identifier
(inclusive). -
#rootdir ⇒ Object
Returns the HistoricFile representing the root of the repo.
-
#supports_trigger? ⇒ Boolean
(also: #can_install_trigger?)
Whether triggers are supported by this SCM.
-
#to_identifier(raw_identifier) ⇒ Object
Transforms
raw_identifier
into the native rype used for revisions. -
#to_yaml_properties ⇒ Object
:nodoc:.
-
#transactional? ⇒ Boolean
(also: #atomic?)
Whether or not this SCM is transactional (atomic).
-
#trigger_installed?(trigger_command, install_dir) ⇒ Boolean
Whether the command denoted by
trigger_command
is installed in the SCM. -
#trigger_mechanism ⇒ Object
Descriptive name of the trigger mechanism.
-
#uninstall_trigger(trigger_command, install_dir) ⇒ Object
Uninstalls
trigger_command
from the SCM. -
#update_commandline(to_identifier = Time.infinity) ⇒ Object
The command line to run in order to update a working copy.
-
#uptodate?(identifier) ⇒ Boolean
Whether the working copy is in synch with the central repository’s revision/time identified by
identifier
.
Instance Attribute Details
#default_options ⇒ Object
Returns the value of attribute default_options.
31 32 33 |
# File 'lib/rscm/base.rb', line 31 def @default_options end |
#logger ⇒ Object
Returns the value of attribute logger.
3 4 5 |
# File 'lib/rscm/revision_poller.rb', line 3 def logger @logger end |
Instance Method Details
#==(other_scm) ⇒ Object
256 257 258 259 260 261 262 |
# File 'lib/rscm/base.rb', line 256 def ==(other_scm) return false if self.class != other_scm.class self.instance_variables.each do |var| return false if self.instance_eval(var) != other_scm.instance_eval(var) end true end |
#add(relative_filename, options = {}) ⇒ Object
Adds relative_filename
to the working copy.
99 100 101 |
# File 'lib/rscm/base.rb', line 99 def add(relative_filename, ={}) raise NotImplementedError end |
#can_create_central? ⇒ Boolean
Whether a repository can be created.
94 95 96 |
# File 'lib/rscm/base.rb', line 94 def can_create_central? false end |
#central_exists?(options = {}) ⇒ Boolean
Whether or not the SCM represented by this instance exists.
62 63 64 65 66 |
# File 'lib/rscm/base.rb', line 62 def central_exists?(={}) # The default implementation assumes yes - override if it can be # determined programmatically. true end |
#checked_out? ⇒ Boolean
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
198 199 200 |
# File 'lib/rscm/base.rb', line 198 def checked_out? File.exists?(@checkout_dir) end |
#checkout(to_identifier = Time.infinity, options = {}) ⇒ Object
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 if one doesn’t already exist. Then the contents of the central SCM should be pulled into the working copy.
The to_identifier
parameter may be optionally specified to obtain files up to a particular time or label. to_identifier
should either be a Time (in UTC - according to the clock on the SCM machine) or a String - reprsenting a label or revision.
This method will yield the relative file name of each checked out file, and also return them in an array. Only files, not directories, should be yielded/returned.
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).
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/rscm/base.rb', line 141 def checkout(to_identifier=Time.infinity, ={}) # :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, ) files = Dir["#{@checkout_dir}/**/*"] added = [] files.each do |file| added << file if File.mtime(file).utc > before_checkout_time end ignore_paths.each do |regex| added.delete_if{|path| path =~ regex} 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 end |
#checkout_commandline(to_identifier = Time.infinity) ⇒ Object
The command line to run in order to check out a fresh working copy.
240 241 242 |
# File 'lib/rscm/base.rb', line 240 def checkout_commandline(to_identifier=Time.infinity) raise NotImplementedError end |
#checkout_dir ⇒ Object
Gets the working copy directory.
45 46 47 |
# File 'lib/rscm/base.rb', line 45 def checkout_dir @checkout_dir end |
#checkout_dir=(dir) ⇒ Object
Sets the checkout dir (working copy). Should be set prior to most other method invocations (depending on the implementation).
40 41 42 |
# File 'lib/rscm/base.rb', line 40 def checkout_dir=(dir) @checkout_dir = PathConverter.filepath_to_nativepath(dir, false) end |
#commit(message, options = {}) ⇒ Object
Commit (check in) modified files.
121 122 123 |
# File 'lib/rscm/base.rb', line 121 def commit(, ={}) raise NotImplementedError end |
#create_central(options = {}) ⇒ Object
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 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).
82 83 84 |
# File 'lib/rscm/base.rb', line 82 def create_central(={}) raise NotImplementedError end |
#destroy_central ⇒ Object
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 it out for good!
89 90 91 |
# File 'lib/rscm/base.rb', line 89 def destroy_central raise NotImplementedError end |
#destroy_working_copy(options = {}) ⇒ Object
Destroys the working copy
57 58 59 |
# File 'lib/rscm/base.rb', line 57 def destroy_working_copy(={}) FileUtils.rm_rf(checkout_dir) unless checkout_dir.nil? end |
#diff(change, &block) ⇒ Object
Returns/yields an IO containing the unified diff of the change. Also see RevisionFile#diff
252 253 254 |
# File 'lib/rscm/base.rb', line 252 def diff(change, &block) raise NotImplementedError end |
#edit(file, options = {}) ⇒ Object
Open a file for edit - required by scms that check out files in read-only mode e.g. perforce
117 118 |
# File 'lib/rscm/base.rb', line 117 def edit(file, ={}) end |
#file(relative_path, dir) ⇒ Object
Returns a HistoricFile for relative_path
178 179 180 |
# File 'lib/rscm/base.rb', line 178 def file(relative_path, dir) HistoricFile.new(relative_path, dir, self) end |
#import_central(options) ⇒ Object
Recursively imports files from :dir
into the central scm, using commit message :message
112 113 114 |
# File 'lib/rscm/base.rb', line 112 def import_central() raise NotImplementedError end |
#install_trigger(trigger_command, install_dir) ⇒ Object
Installs trigger_command
in the SCM. The install_dir
parameter should be an empty local directory that the SCM can use for temporary files if necessary (CVS needs this to check out its administrative files). Most implementations will ignore this parameter.
222 223 224 |
# File 'lib/rscm/base.rb', line 222 def install_trigger(trigger_command, install_dir) raise NotImplementedError end |
#move(relative_src, relative_dest, options = {}) ⇒ Object
Schedules a move of relative_src
to relative_dest
Should not take effect in the central repository until commit
is invoked.
106 107 108 |
# File 'lib/rscm/base.rb', line 106 def move(relative_src, relative_dest, ={}) raise NotImplementedError end |
#open(revision_file, &block) ⇒ Object
Opens a revision_file
183 184 185 |
# File 'lib/rscm/base.rb', line 183 def open(revision_file, &block) #:yield: io raise NotImplementedError end |
#poll_new_revisions(options) ⇒ Object
Polls new revisions for since last_revision
, or if last_revision
is nil, polls since ‘now’ - options[:seconds_before_now]
. If no revisions are found AND the poll was using options[:seconds_before_now]
(i.e. it’s the first poll, and no revisions were found), calls itself recursively with twice the options[:seconds_before_now]
. This happens until revisions are found, ot until the options[:seconds_before_now]
Exceeds 32 weeks, which means it’s probably not worth looking further in the past, the scm is either completely idle or not yet active.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/rscm/revision_poller.rb', line 19 def poll_new_revisions() = { :latest_revision => nil, :quiet_period => DEFAULT_QUIET_PERIOD, :seconds_before_now => TWO_WEEKS_AGO, :max_time_before_now => THIRTY_TWO_WEEKS_AGO, }.merge() max_past = Time.new.utc - [:max_time_before_now] # Default value for start time (in case there are no detected revisions yet) from = Time.new.utc - [:seconds_before_now] if([:latest_revision]) from = [:latest_revision].identifier else if(from < max_past) logger.info "Checked for revisions as far back as #{max_past}. There were none, so we give up." if logger return [] else logger.info "Latest revision is not known. Checking for revisions since: #{from}" if logger end end logger.info "Polling revisions after #{from} (#{from.class.name})" if logger revisions = revisions(from, ) if(revisions.empty?) logger.info "No new revisions after #{from}" if logger unless([:latest_revision]) double_seconds_before_now = 2*[:seconds_before_now] logger.info "Last revision still not found, checking since #{double_seconds_before_now.ago}" if logger new_opts = .dup new_opts[:seconds_before_now] = double_seconds_before_now return poll_new_revisions(new_opts) end else logger.info "There were #{revisions.length} new revision(s) after #{from}" if logger end if(!revisions.empty? && !transactional?) # We're dealing with a non-transactional SCM (like CVS/StarTeam/ClearCase, # unlike Subversion/Monotone). Sleep a little, get the revisions again. # When the revisions are not changing, we can consider the last commit done # and the quiet period elapsed. This is not 100% failsafe, but will work # under most circumstances. In the worst case, we'll miss some files in # the revisions for really slow commits, but they will be part of the next # revision (on next poll). commit_in_progress = true while(commit_in_progress) logger.info "Sleeping for #{[:quiet_period]} seconds because #{visual_name} is not transactional." if logger sleep([:quiet_period]) previous_revisions = revisions revisions = revisions(from) commit_in_progress = revisions != previous_revisions if(commit_in_progress) logger.info "Commit still in progress." if logger end end logger.info "Quiet period elapsed" if logger end return revisions end |
#revisions(from_identifier, options = {}) ⇒ Object
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.
168 169 170 |
# File 'lib/rscm/base.rb', line 168 def revisions(from_identifier, ={}) raise NotImplementedError end |
#rootdir ⇒ Object
Returns the HistoricFile representing the root of the repo
173 174 175 |
# File 'lib/rscm/base.rb', line 173 def rootdir file("", true) end |
#supports_trigger? ⇒ Boolean Also known as: can_install_trigger?
Whether triggers are supported by this SCM. A trigger is a command that can be executed upon a completed commit to the SCM.
204 205 206 207 208 |
# File 'lib/rscm/base.rb', line 204 def supports_trigger? # The default implementation assumes no - override if it can be # determined programmatically. false end |
#to_identifier(raw_identifier) ⇒ Object
Transforms raw_identifier
into the native rype used for revisions.
34 35 36 |
# File 'lib/rscm/base.rb', line 34 def to_identifier(raw_identifier) raw_identifier.to_s end |
#to_yaml_properties ⇒ Object
:nodoc:
49 50 51 52 53 54 |
# File 'lib/rscm/base.rb', line 49 def to_yaml_properties #:nodoc: props = instance_variables props.delete("@checkout_dir") props.delete("@default_options") props.sort! end |
#transactional? ⇒ Boolean Also known as: atomic?
Whether or not this SCM is transactional (atomic).
69 70 71 |
# File 'lib/rscm/base.rb', line 69 def transactional? false end |
#trigger_installed?(trigger_command, install_dir) ⇒ Boolean
Whether the command denoted by trigger_command
is installed in the SCM.
234 235 236 |
# File 'lib/rscm/base.rb', line 234 def trigger_installed?(trigger_command, install_dir) raise NotImplementedError end |
#trigger_mechanism ⇒ Object
Descriptive name of the trigger mechanism
212 213 214 |
# File 'lib/rscm/base.rb', line 212 def trigger_mechanism raise NotImplementedError end |
#uninstall_trigger(trigger_command, install_dir) ⇒ Object
Uninstalls trigger_command
from the SCM.
228 229 230 |
# File 'lib/rscm/base.rb', line 228 def uninstall_trigger(trigger_command, install_dir) raise NotImplementedError end |
#update_commandline(to_identifier = Time.infinity) ⇒ Object
The command line to run in order to update a working copy.
246 247 248 |
# File 'lib/rscm/base.rb', line 246 def update_commandline(to_identifier=Time.infinity) raise NotImplementedError end |
#uptodate?(identifier) ⇒ Boolean
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.
191 192 193 |
# File 'lib/rscm/base.rb', line 191 def uptodate?(identifier) raise NotImplementedError end |