require "svn/core"
require "svn/fs"
require "svn/delta"
require "svn/repos"
def basename(path)
path.chomp("/")
end
class SvnLook
def initialize(path, rev, txn)
@fs = Svn::Repos.open(basename(path)).fs
if txn
@txn = @fs.open_txn(txn)
else
@txn = nil
rev ||= @fs.youngest_rev
end
@rev = rev
end
def run(cmd, *args)
dispatch(cmd, *args)
end
private
def dispatch(cmd, *args)
if respond_to?("cmd_#{cmd}", true)
begin
__send__("cmd_#{cmd}", *args)
rescue ArgumentError
puts $!.message
puts $@
puts("invalid argument for #{cmd}: #{args.join(' ')}")
end
else
puts("unknown command: #{cmd}")
end
end
def cmd_default
cmd_info
cmd_tree
end
def cmd_author
puts(property(Svn::Core::PROP_REVISION_AUTHOR) || "")
end
def cmd_cat
end
def cmd_changed
print_tree(ChangedEditor, nil, true)
end
def cmd_date
if @txn
puts
else
date = property(Svn::Core::PROP_REVISION_DATE)
if date
puts date.strftime('%Y-%m-%d %H:%M(%Z)')
else
puts
end
end
end
def cmd_diff
print_tree(DiffEditor, nil, true)
end
def cmd_dirs_changed
print_tree(DirsChangedEditor)
end
def cmd_ids
print_tree(Editor, 0, true)
end
def cmd_info
cmd_author
cmd_date
cmd_log(true)
end
def cmd_log(print_size=false)
log = property(Svn::Core::PROP_REVISION_LOG) || ''
puts log.length if print_size
puts log
end
def cmd_tree
print_tree(Editor, 0)
end
def cmd_uuid
puts @fs.uuid
end
def cmd_youngest
puts @fs.youngest_rev
end
def property(name)
if @txn
@txn.prop(name)
else
@fs.prop(name, @rev)
end
end
def print_tree(editor_class, base_rev=nil, pass_root=false)
if base_rev.nil?
if @txn
base_rev = @txn.base_revision
else
base_rev = @rev - 1
end
end
if @txn
root = @txn.root
else
root = @fs.root(@rev)
end
base_root = @fs.root(base_rev)
if pass_root
editor = editor_class.new(root, base_root)
else
editor = editor_class.new
end
base_root.dir_delta('', '', root, '', editor)
end
class Editor < Svn::Delta::BaseEditor
def initialize(root=nil, base_root=nil)
@root = root
@indent = ""
end
def open_root(base_revision)
puts "/#{id('/')}"
@indent << ' '
end
def add_directory(path, *args)
puts "#{@indent}#{basename(path)}/#{id(path)}"
@indent << ' '
end
alias open_directory add_directory
def close_directory(baton)
@indent.chop!
end
def add_file(path, *args)
puts "#{@indent}#{basename(path)}#{id(path)}"
end
alias open_file add_file
private
def id(path)
if @root
fs_id = @root.node_id(path)
" <#{fs_id.unparse}>"
else
""
end
end
end
class DirsChangedEditor < Svn::Delta::ChangedDirsEditor
private
def dir_changed(baton)
if baton[0]
puts baton[1] + '/'
baton[0] = nil
end
end
end
class ChangedEditor < Svn::Delta::BaseEditor
def initialize(root, base_root)
@root = root
@base_root = base_root
end
def open_root(base_revision)
[true, '']
end
def delete_entry(path, revision, parent_baton)
print "D #{path}"
if @base_root.dir?('/' + path)
puts "/"
else
puts
end
end
def add_directory(path, parent_baton,
copyfrom_path, copyfrom_revision)
puts "A #{path}/"
[false, path]
end
def open_directory(path, parent_baton, base_revision)
[true, path]
end
def change_dir_prop(dir_baton, name, value)
if dir_baton[0]
puts "_U #{dir_baton[1]}/"
dir_baton[0] = false
end
end
def add_file(path, parent_baton,
copyfrom_path, copyfrom_revision)
puts "A #{path}"
['_', ' ', nil]
end
def open_file(path, parent_baton, base_revision)
['_', ' ', path]
end
def apply_textdelta(file_baton, base_checksum)
file_baton[0] = 'U'
nil
end
def change_file_prop(file_baton, name, value)
file_baton[1] = 'U'
end
def close_file(file_baton, text_checksum)
text_mod, prop_mod, path = file_baton
if path
status = text_mod + prop_mod
if status != '_ '
puts "#{status} #{path}"
end
end
end
end
class DiffEditor < Svn::Delta::BaseEditor
def initialize(root, base_root)
@root = root
@base_root = base_root
end
def delete_entry(path, revision, parent_baton)
unless @base_root.dir?('/' + path)
do_diff(path, nil)
end
end
def add_file(path, parent_baton,
copyfrom_path, copyfrom_revision)
do_diff(nil, path)
['_', ' ', nil]
end
def open_file(path, parent_baton, base_revision)
['_', ' ', path]
end
def apply_textdelta(file_baton, base_checksum)
if file_baton[2].nil?
nil
else
do_diff(file_baton[2], file_baton[2])
end
end
private
def do_diff(base_path, path)
if base_path.nil?
puts("Added: #{path}")
name = path
elsif path.nil?
puts("Removed: #{base_path}")
name = base_path
else
puts "Modified: #{path}"
name = path
end
base_label = "#{name} (original)"
label = "#{name} (new)"
puts "=" * 78
differ = Svn::Fs::FileDiff.new(@base_root, base_path, @root, path)
puts differ.unified(base_label, label)
puts
end
end
end
def usage
messages = [
"usage: #{$0} REPOS_PATH rev REV [COMMAND] - inspect revision REV",
" #{$0} REPOS_PATH txn TXN [COMMAND] - inspect transaction TXN",
" #{$0} REPOS_PATH [COMMAND] - inspect the youngest revision",
"",
"REV is a revision number > 0.",
"TXN is a transaction name.",
"",
"If no command is given, the default output (which is the same as",
"running the subcommands `info' then `tree') will be printed.",
"",
"COMMAND can be one of: ",
"",
" author: print author.",
" changed: print full change summary: all dirs & files changed.",
" date: print the timestamp (revisions only).",
" diff: print GNU-style diffs of changed files and props.",
" dirs-changed: print changed directories.",
" ids: print the tree, with nodes ids.",
" info: print the author, data, log_size, and log message.",
" log: print log message.",
" tree: print the tree.",
" uuid: print the repository's UUID (REV and TXN ignored).",
" youngest: print the youngest revision number (REV and TXN ignored).",
]
puts(messages.join("\n"))
exit(1)
end
if ARGV.empty?
usage
end
path = ARGV.shift
cmd = ARGV.shift
rev = nil
txn = nil
case cmd
when "rev"
rev = Integer(ARGV.shift)
cmd = ARGV.shift
when "txn"
txn = ARGV.shift
cmd = ARGV.shift
end
cmd ||= "default"
cmd = cmd.gsub(/-/, '_')
SvnLook.new(path, rev, txn).run(cmd)