blob: 955bb1c8c6f8f611046e465d0746f5d6a47d00b0 [file] [log] [blame]
#!/usr/bin/env ruby
require "svn/core"
require "svn/fs"
require "svn/delta"
require "svn/repos"
def basename(path)
path.chomp("/")
end
class SvnLook
def initialize(pool, path, rev, txn)
@pool = pool
@fs = Svn::Repos.open(path, @pool).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
time = str_to_time(date)
puts time.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 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.editor = editor
base_root.dir_delta('', '', root, '')
end
def str_to_time(str)
Svn::Util.string_to_time(str, @pool)
end
class Editor < Svn::Delta::Editor
def initialize(root=nil, base_root=nil)
@root = root
# base_root ignored
@indent = ""
end
def open_root(base_revision, dir_pool)
puts "/#{id('/', dir_pool)}"
@indent << ' '
end
def add_directory(path, *args)
puts "#{@indent}#{basename(path)}/#{id(path, args[-1])}"
@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, args[-1])}"
end
alias open_file add_file
private
def id(path, pool)
if @root
fs_id = @root.node_id(path, pool)
" <#{fs_id.unparse(pool)}>"
else
""
end
end
end
class DirsChangedEditor < Svn::Delta::Editor
def open_root(base_revision, dir_pool)
[true, '']
end
def delete_entry(path, revision, parent_baton, pool)
dir_changed(parent_baton)
end
def add_directory(path, parent_baton,
copyfrom_path, copyfrom_revision, dir_pool)
dir_changed(parent_baton)
[true, path]
end
def open_directory(path, parent_baton, base_revision, dir_pool)
[true, path]
end
def change_dir_prop(dir_baton, name, value, pool)
dir_changed(dir_baton)
end
def add_file(path, parent_baton,
copyfrom_path, copyfrom_revision, file_pool)
dir_changed(parent_baton)
end
def open_file(path, parent_baton, base_revision, file_pool)
dir_changed(parent_baton)
end
private
def dir_changed( baton)
if baton[0]
# the directory hasn't been printed yet. do it.
puts baton[1] + '/'
baton[0] = nil
end
end
end
class ChangedEditor < Svn::Delta::Editor
def initialize(root, base_root)
@root = root
@base_root = base_root
end
def open_root(base_revision, dir_pool)
[true, '']
end
def delete_entry(path, revision, parent_baton, pool)
print "D #{path}"
if @base_root.dir?('/' + path, pool)
puts "/"
else
puts
end
end
def add_directory(path, parent_baton,
copyfrom_path, copyfrom_revision, dir_pool)
puts "A #{path}/"
[false, path]
end
def open_directory(path, parent_baton, base_revision, dir_pool)
[true, path]
end
def change_dir_prop(dir_baton, name, value, pool)
if dir_baton[0]
# the directory hasn't been printed yet. do it.
puts "_U #{dir_baton[1]}/"
dir_baton[0] = false
end
end
def add_file(path, parent_baton,
copyfrom_path, copyfrom_revision, file_pool)
puts "A #{path}"
['_', ' ', nil]
end
def open_file(path, parent_baton, base_revision, file_pool)
['_', ' ', path]
end
def apply_textdelta(file_baton, base_checksum, pool)
file_baton[0] = 'U'
nil
end
def change_file_prop(file_baton, name, value, pool)
file_baton[1] = 'U'
end
def close_file(file_baton, text_checksum)
text_mod, prop_mod, path = file_baton
# test the path. it will be None if we added this file.
if path
status = text_mod + prop_mod
# was there some kind of change?
if status != '_ '
puts "#{status} #{path}"
end
end
end
end
class DiffEditor < Svn::Delta::Editor
def initialize(root, base_root)
@root = root
@base_root = base_root
end
def delete_entry(path, revision, parent_baton, pool)
unless @base_root.dir?('/' + path, pool)
do_diff(path, nil, pool)
end
end
def add_file(path, parent_baton,
copyfrom_path, copyfrom_revision, file_pool)
do_diff(nil, path, file_pool)
['_', ' ', nil, file_pool]
end
def open_file(path, parent_baton, base_revision, file_pool)
['_', ' ', path, file_pool]
end
def apply_textdelta(file_baton, base_checksum, pool)
if file_baton[2].nil?
nil
else
do_diff(file_baton[2], file_baton[2], file_baton[3])
end
end
private
def do_diff(base_path, path, pool)
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)"
differ = Svn::Fs::FileDiff.new(@base_root, base_path, @root, path, pool)
puts "=" * 78
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.",
]
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"
Svn::Core::Pool.new do |pool|
SvnLook.new(pool, path, rev, txn).run(cmd.gsub(/-/, '_'))
end