diff options
author | James McCoy <jamessan@debian.org> | 2018-07-31 22:26:52 -0400 |
---|---|---|
committer | James McCoy <jamessan@debian.org> | 2018-07-31 22:26:52 -0400 |
commit | e20a507113ff1126aeb4a97b806390ea377fe292 (patch) | |
tree | 0260b3a40387d7f994fbadaf22f1e9d3c080b09f /tools/examples/svnshell.rb | |
parent | c64debffb81d2fa17e9a72af7199ccf88b3cc556 (diff) |
New upstream version 1.10.2
Diffstat (limited to 'tools/examples/svnshell.rb')
-rwxr-xr-x | tools/examples/svnshell.rb | 456 |
1 files changed, 456 insertions, 0 deletions
diff --git a/tools/examples/svnshell.rb b/tools/examples/svnshell.rb new file mode 100755 index 0000000..a49000e --- /dev/null +++ b/tools/examples/svnshell.rb @@ -0,0 +1,456 @@ +#!/usr/bin/env ruby +# +# svnshell.rb : a Ruby-based shell interface for cruising 'round in +# the filesystem. +# +# Usage: ruby svnshell.rb REPOS_PATH, where REPOS_PATH is a path to +# a repository on your local filesystem. +# +# NOTE: This program requires the Ruby readline extension. +# See http://wiki.rubyonrails.com/rails/show/ReadlineLibrary +# for details on how to install readline for Ruby. +# +###################################################################### +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +###################################################################### +# + +require "readline" +require "shellwords" + +require "svn/fs" +require "svn/core" +require "svn/repos" + +# SvnShell: a Ruby-based shell interface for cruising 'round in +# the filesystem. +class SvnShell + + # A list of potential commands. This list is populated by + # the 'method_added' function (see below). + WORDS = [] + + # Check for methods that start with "do_" + # and list them as potential commands + class << self + def method_added(name) + if /^do_(.*)$/ =~ name.to_s + WORDS << $1 + end + end + end + + # Constructor for SvnShell + # + # path: The path to a Subversion repository + def initialize(path) + @repos_path = path + @path = "/" + self.rev = youngest_rev + @exited = false + end + + # Run the shell + def run + + # While the user hasn't typed 'exit' and there is still input to be read + while !@exited and buf = Readline.readline(prompt, true) + + # Parse the command line into a single command and arguments + cmd, *args = Shellwords.shellwords(buf) + + # Skip empty lines + next if /\A\s*\z/ =~ cmd.to_s + + # Open a new connection to the repo + @fs = Svn::Repos.open(@repos_path).fs + setup_root + + # Execute the specified command + dispatch(cmd, *args) + + # Find a path that exists in the current revision + @path = find_available_path + + # Close the connection to the repo + @root.close + + end + end + + # Private functions + private + + # Get the current prompt string + def prompt + + # Gather data for the prompt string + if rev_mode? + mode = "rev" + info = @rev + else + mode = "txn" + info = @txn + end + + # Return the prompt string + "<#{mode}: #{info} #{@path}>$ " + end + + # Dispatch a command to the appropriate do_* subroutine + def dispatch(cmd, *args) + + # Dispatch cmd to the appropriate do_* function + if respond_to?("do_#{cmd}", true) + begin + __send__("do_#{cmd}", *args) + rescue ArgumentError + # puts $!.message + # puts $@ + puts("Invalid argument for #{cmd}: #{args.join(' ')}") + end + else + puts("Unknown subcommand: #{cmd}") + puts("Try one of these commands: ", WORDS.sort.join(" ")) + end + end + + # Output the contents of a file from the repository + def do_cat(path) + + # Normalize the path to an absolute path + normalized_path = normalize_path(path) + + # Check what type of node exists at the specified path + case @root.check_path(normalized_path) + when Svn::Core::NODE_NONE + puts "Path '#{normalized_path}' does not exist." + when Svn::Core::NODE_DIR + puts "Path '#{normalized_path}' is not a file." + else + # Output the file to standard out + @root.file_contents(normalized_path) do |stream| + puts stream.read(@root.file_length(normalized_path)) + end + end + end + + # Set the current directory + def do_cd(path="/") + + # Normalize the path to an absolute path + normalized_path = normalize_path(path) + + # If it's a valid directory, then set the directory + if @root.check_path(normalized_path) == Svn::Core::NODE_DIR + @path = normalized_path + else + puts "Path '#{normalized_path}' is not a valid filesystem directory." + end + end + + # List the contents of the current directory or provided paths + def do_ls(*paths) + + # Default to listing the contents of the current directory + paths << @path if paths.empty? + + # Foreach path + paths.each do |path| + + # Normalize the path to an absolute path + normalized_path = normalize_path(path) + + # Is it a directory or file? + case @root.check_path(normalized_path) + when Svn::Core::NODE_DIR + + # Output the contents of the directory + parent = normalized_path + entries = @root.dir_entries(parent) + + when Svn::Core::NODE_FILE + + # Split the path into directory and filename components + parts = path_to_parts(normalized_path) + name = parts.pop + parent = parts_to_path(parts) + + # Output the filename + puts "#{parent}:#{name}" + + # Double check that the file exists + # inside the parent directory + parent_entries = @root.dir_entries(parent) + if parent_entries[name].nil? + # Hmm. We found the file, but it doesn't exist inside + # the parent directory. That's a bit unusual. + puts "No directory entry found for '#{normalized_path}'" + next + else + # Save the path so it can be output in detail + entries = {name => parent_entries[name]} + end + else + # Path is not a directory or a file, + # so it must not exist + puts "Path '#{normalized_path}' not found." + next + end + + # Output a detailed listing of the files we found + puts " REV AUTHOR NODE-REV-ID SIZE DATE NAME" + puts "-" * 76 + + # For each entry we found... + entries.keys.sort.each do |entry| + + # Calculate the full path to the directory entry + fullpath = parent + '/' + entry + if @root.dir?(fullpath) + # If it's a directory, output an extra slash + size = '' + name = entry + '/' + else + # If it's a file, output the size of the file + size = @root.file_length(fullpath).to_i.to_s + name = entry + end + + # Output the entry + node_id = entries[entry].id.to_s + created_rev = @root.node_created_rev(fullpath) + author = @fs.prop(Svn::Core::PROP_REVISION_AUTHOR, created_rev).to_s + date = @fs.prop(Svn::Core::PROP_REVISION_DATE, created_rev) + args = [ + created_rev, author[0,8], + node_id, size, date.strftime("%b %d %H:%M(%Z)"), name + ] + puts "%6s %8s <%10s> %8s %17s %s" % args + + end + end + end + + # List all currently open transactions available for browsing + def do_lstxns + + # Get a sorted list of open transactions + txns = @fs.transactions + txns.sort + counter = 0 + + # Output the open transactions + txns.each do |txn| + counter = counter + 1 + puts "%8s " % txn + + # Every six transactions, output an extra newline + if counter == 6 + puts + counter = 0 + end + end + puts + end + + # Output the properties of a particular path + def do_pcat(path=nil) + + # Default to the current directory + catpath = path ? normalize_path(path) : @path + + # Make sure that the specified path exists + if @root.check_path(catpath) == Svn::Core::NODE_NONE + puts "Path '#{catpath}' does not exist." + return + end + + # Get the list of properties + plist = @root.node_proplist(catpath) + return if plist.nil? + + # Output each property + plist.each do |key, value| + puts "K #{key.size}" + puts key + puts "P #{value.size}" + puts value + end + + # That's all folks! + puts 'PROPS-END' + + end + + # Set the current revision to view + def do_setrev(rev) + + # Make sure the specified revision exists + begin + @fs.root(Integer(rev)).close + rescue Svn::Error + puts "Error setting the revision to '#{rev}': #{$!.message}" + return + end + + # Set the revision + self.rev = Integer(rev) + + end + + # Open an existing transaction to view + def do_settxn(name) + + # Make sure the specified transaction exists + begin + txn = @fs.open_txn(name) + txn.root.close + rescue Svn::Error + puts "Error setting the transaction to '#{name}': #{$!.message}" + return + end + + # Set the transaction + self.txn = name + + end + + # List the youngest revision available for browsing + def do_youngest + rev = @fs.youngest_rev + puts rev + end + + # Exit this program + def do_exit + @exited = true + end + + # Find the youngest revision + def youngest_rev + Svn::Repos.open(@repos_path).fs.youngest_rev + end + + # Set the current revision + def rev=(new_value) + @rev = new_value + @txn = nil + reset_root + end + + # Set the current transaction + def txn=(new_value) + @txn = new_value + reset_root + end + + # Check whether we are in 'revision-mode' + def rev_mode? + @txn.nil? + end + + # Close the current root and setup a new one + def reset_root + if @root + @root.close + setup_root + end + end + + # Setup a new root + def setup_root + if rev_mode? + @root = @fs.root(@rev) + else + @root = @fs.open_txn(name).root + end + end + + # Convert a path into its component parts + def path_to_parts(path) + path.split(/\/+/) + end + + # Join the component parts of a path into a string + def parts_to_path(parts) + normalized_parts = parts.reject{|part| part.empty?} + "/#{normalized_parts.join('/')}" + end + + # Convert a path to a normalized, absolute path + def normalize_path(path) + + # Convert the path to an absolute path + if path[0,1] != "/" and @path != "/" + path = "#{@path}/#{path}" + end + + # Split the path into its component parts + parts = path_to_parts(path) + + # Build a list of the normalized parts of the path + normalized_parts = [] + parts.each do |part| + case part + when "." + # ignore + when ".." + normalized_parts.pop + else + normalized_parts << part + end + end + + # Join the normalized parts together into a string + parts_to_path(normalized_parts) + + end + + # Find the parent directory of a specified path + def parent_dir(path) + normalize_path("#{path}/..") + end + + # Try to land on the specified path as a directory. + # If the specified path does not exist, look for + # an ancestor path that does exist. + def find_available_path(path=@path) + if @root.check_path(path) == Svn::Core::NODE_DIR + path + else + find_available_path(parent_dir(path)) + end + end + +end + + +# Autocomplete commands +Readline.completion_proc = Proc.new do |word| + SvnShell::WORDS.grep(/^#{Regexp.quote(word)}/) +end + +# Output usage information if necessary +if ARGV.size != 1 + puts "Usage: #{$0} REPOS_PATH" + exit(1) +end + +# Create a new SvnShell with the command-line arguments and run it +SvnShell.new(ARGV.shift).run |