#!/usr/bin/ruby # Used to prepare a directory for commit to Subversion. This is necessary for certain file types on Mac OS X because what appear to be files in the Finder # are actually directories (Mac uses the term "bundle" for this concept). It is useless to put the .svn folder inside such a directory, because it will # tend to be deleted whenever the "file" is saved. # # Instead, we want to compress the directory to a single archive file; the bundle will be marked as svn:ignore. # # We use tar with Bzip2 compression, which is resource intensive to create, but compresses much better than GZip or PKZip. # # The trick is that we only want to create the archive version when necessary; when the archive does not exist, or when any file # in the bundle is newer than the archive. require 'optparse' # Set via command line options $extensions = %w{pages key oo3 graffle} $recursive = true $dry_run = false # Queue of folders to search (for bundles) $queue = [] def matching_extension(name) dotx = name.rindex('.') return false unless dotx ext = name[dotx + 1 .. -1] return $extensions.include?(ext) end # Iterate over the directory, identify bundles that may need to be compressed and (if recursive) subdirectories # to search. # # path: string path for a directory def search_directory(dirpath) Dir.foreach(dirpath) do |name| # Skip hidden files and directories next if name[0..0] == "." path = File.join(dirpath, name) next unless File.directory?(path) if matching_extension(name) update_archive path next end if $recursive $queue << path end end end def needs_update(bundle_path, archive_path) return true unless File.exists?(archive_path) archive_mtime = File.mtime(archive_path) # The archive exists ... can we find a file inside the bundle thats newer? # This won't catch deletions, but that's ok. Bundles tend to get completly # overwritten when any tiny thing changes. dirqueue = [bundle_path] until dirqueue.empty? dirpath = dirqueue.pop Dir.foreach(dirpath) do |name| path = File.join(dirpath, name) if File.directory?(path) dirqueue << path unless [".", ".."].include?(name) next end # Is this file newer? if File.mtime(path) > archive_mtime return true end end end return false end def update_archive(path) archive = path + ".tar.bz2" return unless needs_update(path, archive) if $dry_run puts "Would create #{archive}" return end puts "Creating #{archive}" dir = File.dirname(path) bundle = File.basename(path) # Could probably fork and do it in a subshell system "tar --create --file=#{archive} --bzip2 --directory=#{dir} #{bundle}" end $opts = OptionParser.new do |opts| opts.banner = "Usage: prepsvn [options]" opts.on("-d", "--dir DIR", "Add directory to search (if no directory specify, current directory is searched)") do |value| $queue << value end opts.on("-e", "--extension EXTENSION", "Add another extension to match when searching for bundles to archive") do |value| $extensions << value end opts.on("-N", "--non-recursive", "Do not search non-bundle sub directories for files to archive") do $recursive = false end opts.on("-D", "--dry-run", "Identify what archives would be created") do $dry_run = true end opts.on("-h", "--help", "Help for this command") do puts opts exit end end def fail(message) puts "Error: #{message}" puts $opts end begin $opts.parse! rescue OptionParser::InvalidOption fail $! end # If no --dir specified, use the current directory. if $queue.empty? $queue << Dir.getwd end until $queue.empty? search_directory $queue.pop end