# This file Copyright Tim Coulter, 03/07/2007. # # You are free to modify and use this file under the terms of the GNU LGPL. # You can find the latest version of the LGPL here: # # http://www.gnu.org/licenses/lgpl.txt # # This library 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 # Lesser General Public License for more details. require "svn/repos" require "svn/core" require "fileutils" class SvnRepos attr_reader :repos_path # Translate SVN Constants to more programmer friendly terms. CONSTANTS = {:author => Svn::Core::PROP_REVISION_AUTHOR, :date => Svn::Core::PROP_REVISION_DATE} # Constructor. Either create a repository at the given location, or open it if # it already exists. def initialize(repos_path) @repos_path = repos_path begin File.stat(repos_path + "/format") @repos = Svn::Repos.open(@repos_path) rescue @repos = Svn::Repos.create(@repos_path) end end # Synonym for new. def self.open(repos_path) SvnRepos.new(repos_path) end # Commit a file. This function either takes in a hash with the :path and :data # keys set to their respective values, or it takes in an array of hashes with the # same keys set. Passing an array will let you commit multiple files at once. # # The attributes hash is option, and as of now, is only available for setting the # author of a revisoin. An example would be {:author => "tcoulter"}. # # Note that this function takes care of making sure files and directories are present # within the repository. You don't have to create them; this function will do it for you. def commit(hash, attributes={}) if (hash.is_a? Array) multiple_commit(hash, attributes) else @repos.fs.transaction do |txn| write_file(txn, hash[:path], hash[:data]) set_author_if_present(txn, attributes) end end end # Get the contents of a specific file for specific revision. # If not revision is passed, the most recent data is returned. def file_contents(path, revision_id = nil) if (revision_id != nil) revision_id = revision_id.to_i end begin stream = @repos.fs.root(revision_id).file_contents(path) stream.read rescue nil end end # Get the revision data for a specified path. This will return an array of hashes, # where each hash contains the :path, the :id, and the :date for a specific revision. def file_revs(path) make_revision_info_programmer_friendly(@repos.file_revs(path, 0, @repos.fs.youngest_rev)) end def property(prop, rev=nil) @repos.prop(CONSTANTS[prop], rev) end # Get the diffs for a specific revision. This function can be more programmer friendly, # but as of now, returns the exact output given from the repository. def diffs(rev) info = Svn::Info.new(@repos_path, rev) info.diffs end # Returns the most recent revision of the repository. def youngest_revision @repos.fs.youngest_rev end # Returns the most recent revision for a specified path within the repository. def youngest_revision(path) @repos.fs.root.stat(path).created_rev end # Alias the revision_count with the youngest revision. They happen to be the # same number all the time. alias_method :revision_count, :youngest_revision # Delete the repository from the filesystem and create a noew one. def format! FileUtils.remove_dir(@repos_path, false) Svn::Repos.create(@repos_path) end private # A helper when commiting multiple files at once. def multiple_commit(array_of_hashes, attributes) if (array_of_hashes.length == 0) return end @repos.fs.transaction do |txn| array_of_hashes.each do |hash| write_file(txn, hash[:path], hash[:data]) end set_author_if_present(txn, attributes) end end # Make a directory if it's not already present. def make_directory(txn, path) if (txn.root.check_path(path) == 0) txn.root.make_dir(path) end end # Make a file if it's not already present. def make_file(txn, path) if (txn.root.check_path(path) == 0) txn.root.make_file(path) end end # Set the author if it's present in the attributes hash. def set_author_if_present(txn, attributes) if (attributes.has_key? :author) txn.set_prop(CONSTANTS[:author], attributes[:author]) end end # Helper. This function does the actual writing. def write_file(txn, path, data) pieces = path.split("/").delete_if {|x| x == ""} dir_path = "" (0..pieces.length - 2).each do |index| dir_path += "/" + pieces[index] make_directory(txn, dir_path) end make_file(txn, path) checksum = MD5.new(data).hexdigest stream = txn.root.apply_text(path, checksum) stream.write(data) stream.close end # Helper to make file_revs friendlier. def make_revision_info_programmer_friendly(file_revs) output_array = [] file_revs.each do |rev| output_hash = {} output_hash[:path] = rev[0] output_hash[:id] = rev[1] attribute_hash = rev[2] output_hash[:time] = Time.parse(attribute_hash[CONSTANTS[:date]]) output_array.push(output_hash) end output_array end end