summaryrefslogtreecommitdiff
path: root/tools/examples/svnshell.rb
diff options
context:
space:
mode:
authorJames McCoy <jamessan@debian.org>2018-07-31 22:26:52 -0400
committerJames McCoy <jamessan@debian.org>2018-07-31 22:26:52 -0400
commite20a507113ff1126aeb4a97b806390ea377fe292 (patch)
tree0260b3a40387d7f994fbadaf22f1e9d3c080b09f /tools/examples/svnshell.rb
parentc64debffb81d2fa17e9a72af7199ccf88b3cc556 (diff)
New upstream version 1.10.2
Diffstat (limited to 'tools/examples/svnshell.rb')
-rwxr-xr-xtools/examples/svnshell.rb456
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