source: valtobtest/subversion-1.6.2/tools/examples/svnlook.rb @ 3

Last change on this file since 3 was 3, checked in by valtob, 15 years ago

subversion source 1.6.2 as test

  • Property svn:executable set to *
File size: 12.8 KB
Line 
1#!/usr/bin/env ruby
2#
3# svnlook.rb : a Ruby-based replacement for svnlook
4#
5######################################################################
6#
7# Copyright (c) 2000-2005 CollabNet.  All rights reserved.
8#
9# This software is licensed as described in the file COPYING, which
10# you should have received as part of this distribution.  The terms
11# are also available at http://subversion.tigris.org/license-1.html.
12# If newer versions of this license are posted there, you may use a
13# newer version instead, at your option.
14#
15######################################################################
16#
17
18require "svn/core"
19require "svn/fs"
20require "svn/delta"
21require "svn/repos"
22
23# Chomp off trailing slashes
24def basename(path)
25  path.chomp("/")
26end
27
28# SvnLook: a Ruby-based replacement for svnlook
29class SvnLook
30
31  # Initialize the SvnLook application
32  def initialize(path, rev, txn)
33    # Open a repository
34    @fs = Svn::Repos.open(basename(path)).fs
35
36    # If a transaction was specified, open it
37    if txn
38      @txn = @fs.open_txn(txn)
39    else
40      # Use the latest revision from the repo,
41      # if they haven't specified a revision
42      @txn = nil
43      rev ||= @fs.youngest_rev
44    end
45
46    @rev = rev
47  end
48
49  # Dispatch all commands to appropriate subroutines
50  def run(cmd, *args)
51    dispatch(cmd, *args)
52  end
53
54  private
55
56  # Dispatch all commands to appropriate subroutines
57  def dispatch(cmd, *args)
58    if respond_to?("cmd_#{cmd}", true)
59      begin
60        __send__("cmd_#{cmd}", *args)
61      rescue ArgumentError
62        puts $!.message
63        puts $@
64        puts("invalid argument for #{cmd}: #{args.join(' ')}")
65      end
66    else
67      puts("unknown command: #{cmd}")
68    end
69  end
70
71  # Default command: Run the 'info' and 'tree' commands
72  def cmd_default
73    cmd_info
74    cmd_tree
75  end
76
77  # Print the 'author' of the specified revision or transaction
78  def cmd_author
79    puts(property(Svn::Core::PROP_REVISION_AUTHOR) || "")
80  end
81
82  # Not implemented yet
83  def cmd_cat
84  end
85
86  # Find out what has changed in the specified revision or transaction
87  def cmd_changed
88    print_tree(ChangedEditor, nil, true)
89  end
90
91  # Output the date that the current revision was committed.
92  def cmd_date
93    if @txn
94      # It's not committed yet, so output nothing
95      puts
96    else
97      # Get the time the revision was committed
98      date = property(Svn::Core::PROP_REVISION_DATE)
99
100      if date
101        # Print out the date in a nice format
102        puts date.strftime('%Y-%m-%d %H:%M(%Z)')
103      else
104        # The specified revision doesn't have an associated date.
105        # Output just a blank line.
106        puts
107      end
108    end
109  end
110
111  # Output what changed in the specified revision / transaction
112  def cmd_diff
113    print_tree(DiffEditor, nil, true)
114  end
115
116  # Output what directories changed in the specified revision / transaction
117  def cmd_dirs_changed
118    print_tree(DirsChangedEditor)
119  end
120
121  # Output the tree, with node ids
122  def cmd_ids
123    print_tree(Editor, 0, true)
124  end
125
126  # Output the author, date, and the log associated with the specified
127  # revision / transaction
128  def cmd_info
129    cmd_author
130    cmd_date
131    cmd_log(true)
132  end
133
134  # Output the log message associated with the specified revision / transaction
135  def cmd_log(print_size=false)
136    log = property(Svn::Core::PROP_REVISION_LOG) || ''
137    puts log.length if print_size
138    puts log
139  end
140
141  # Output the tree associated with the provided tree
142  def cmd_tree
143    print_tree(Editor, 0)
144  end
145
146  # Output the repository's UUID.
147  def cmd_uuid
148    puts @fs.uuid
149  end
150
151  # Output the repository's youngest revision.
152  def cmd_youngest
153    puts @fs.youngest_rev
154  end
155
156  # Return a property of the specified revision or transaction.
157  # Name: the ID of the property you want to retrieve.
158  #       E.g. Svn::Core::PROP_REVISION_LOG
159  def property(name)
160    if @txn
161      @txn.prop(name)
162    else
163      @fs.prop(name, @rev)
164    end
165  end
166
167  # Print a tree of differences between two revisions
168  def print_tree(editor_class, base_rev=nil, pass_root=false)
169    if base_rev.nil?
170      if @txn
171        # Output changes since the base revision of the transaction
172        base_rev = @txn.base_revision
173      else
174        # Output changes since the previous revision
175        base_rev = @rev - 1
176      end
177    end
178
179    # Get the root of the specified transaction or revision
180    if @txn
181      root = @txn.root
182    else
183      root = @fs.root(@rev)
184    end
185
186    # Get the root of the base revision
187    base_root = @fs.root(base_rev)
188
189    # Does the provided editor need to know
190    # the revision and base revision we're working with?
191    if pass_root
192      # Create a new editor with the provided root and base_root
193      editor = editor_class.new(root, base_root)
194    else
195      # Create a new editor with nil root and base_roots
196      editor = editor_class.new
197    end
198
199    # Do a directory delta between the two roots with
200    # the specified editor
201    base_root.dir_delta('', '', root, '', editor)
202  end
203
204  # Output the current tree for a specified revision
205  class Editor < Svn::Delta::BaseEditor
206
207    # Initialize the Editor object
208    def initialize(root=nil, base_root=nil)
209      @root = root
210      # base_root ignored
211
212      @indent = ""
213    end
214
215    # Recurse through the root (and increase the indent level)
216    def open_root(base_revision)
217      puts "/#{id('/')}"
218      @indent << ' '
219    end
220
221    # If a directory is added, output this and increase
222    # the indent level
223    def add_directory(path, *args)
224      puts "#{@indent}#{basename(path)}/#{id(path)}"
225      @indent << ' '
226    end
227
228    alias open_directory add_directory
229
230    # If a directory is closed, reduce the ident level
231    def close_directory(baton)
232      @indent.chop!
233    end
234
235    # If a file is added, output that it has been changed
236    def add_file(path, *args)
237      puts "#{@indent}#{basename(path)}#{id(path)}"
238    end
239
240    alias open_file add_file
241
242    # Private methods
243    private
244
245    # Get the node id of a particular path
246    def id(path)
247      if @root
248        fs_id = @root.node_id(path)
249        " <#{fs_id.unparse}>"
250      else
251        ""
252      end
253    end
254  end
255
256
257  # Output directories that have been changed.
258  # In this class, methods such as open_root and add_file
259  # are inherited from Svn::Delta::ChangedDirsEditor.
260  class DirsChangedEditor < Svn::Delta::ChangedDirsEditor
261
262    # Private functions
263    private
264
265    # Print out the name of a directory if it has been changed.
266    # But only do so once.
267    # This behaves in a way like a callback function does.
268    def dir_changed(baton)
269      if baton[0]
270        # The directory hasn't been printed yet,
271        # so print it out.
272        puts baton[1] + '/'
273
274        # Make sure we don't print this directory out twice
275        baton[0] = nil
276      end
277    end
278  end
279
280  # Output files that have been changed between two roots
281  class ChangedEditor < Svn::Delta::BaseEditor
282
283    # Constructor
284    def initialize(root, base_root)
285      @root = root
286      @base_root = base_root
287    end
288
289    # Look at the root node
290    def open_root(base_revision)
291      # Nothing has been printed out yet, so return 'true'.
292      [true, '']
293    end
294
295    # Output deleted files
296    def delete_entry(path, revision, parent_baton)
297      # Output deleted paths with a D in front of them
298      print "D   #{path}"
299
300      # If we're deleting a directory,
301      # indicate this with a trailing slash
302      if @base_root.dir?('/' + path)
303        puts "/"
304      else
305        puts
306      end
307    end
308
309    # Output that a directory has been added
310    def add_directory(path, parent_baton,
311                      copyfrom_path, copyfrom_revision)
312      # Output 'A' to indicate that the directory was added.
313      # Also put a trailing slash since it's a directory.
314      puts "A   #{path}/"
315
316      # The directory has been printed -- don't print it again.
317      [false, path]
318    end
319
320    # Recurse inside directories
321    def open_directory(path, parent_baton, base_revision)
322      # Nothing has been printed out yet, so return true.
323      [true, path]
324    end
325
326    def change_dir_prop(dir_baton, name, value)
327      # Has the directory been printed yet?
328      if dir_baton[0]
329        # Print the directory
330        puts "_U  #{dir_baton[1]}/"
331
332        # Don't let this directory get printed again.
333        dir_baton[0] = false
334      end
335    end
336
337    def add_file(path, parent_baton,
338                 copyfrom_path, copyfrom_revision)
339      # Output that a directory has been added
340      puts "A   #{path}"
341
342      # We've already printed out this entry, so return '_'
343      # to prevent it from being printed again
344      ['_', ' ', nil]
345    end
346
347
348    def open_file(path, parent_baton, base_revision)
349      # Changes have been made -- return '_' to indicate as such
350      ['_', ' ', path]
351    end
352
353    def apply_textdelta(file_baton, base_checksum)
354      # The file has been changed -- we'll print that out later.
355      file_baton[0] = 'U'
356      nil
357    end
358
359    def change_file_prop(file_baton, name, value)
360      # The file has been changed -- we'll print that out later.
361      file_baton[1] = 'U'
362    end
363
364    def close_file(file_baton, text_checksum)
365      text_mod, prop_mod, path = file_baton
366      # Test the path. It will be nil if we added this file.
367      if path
368        status = text_mod + prop_mod
369        # Was there some kind of change?
370        if status != '_ '
371          puts "#{status}  #{path}"
372        end
373      end
374    end
375  end
376
377  # Output diffs of files that have been changed
378  class DiffEditor < Svn::Delta::BaseEditor
379
380    # Constructor
381    def initialize(root, base_root)
382      @root = root
383      @base_root = base_root
384    end
385
386    # Handle deleted files and directories
387    def delete_entry(path, revision, parent_baton)
388      # Print out diffs of deleted files, but not
389      # deleted directories
390      unless @base_root.dir?('/' + path)
391        do_diff(path, nil)
392      end
393    end
394
395    # Handle added files
396    def add_file(path, parent_baton,
397                 copyfrom_path, copyfrom_revision)
398      # If a file has been added, print out the diff.
399      do_diff(nil, path)
400
401      ['_', ' ', nil]
402    end
403
404    # Handle files
405    def open_file(path, parent_baton, base_revision)
406      ['_', ' ', path]
407    end
408
409    # If a file is changed, print out the diff
410    def apply_textdelta(file_baton, base_checksum)
411      if file_baton[2].nil?
412        nil
413      else
414        do_diff(file_baton[2], file_baton[2])
415      end
416    end
417
418    private
419
420    # Print out a diff between two paths
421    def do_diff(base_path, path)
422      if base_path.nil?
423        # If there's no base path, then the file
424        # must have been added
425        puts("Added: #{path}")
426        name = path
427      elsif path.nil?
428        # If there's no new path, then the file
429        # must have been deleted
430        puts("Removed: #{base_path}")
431        name = base_path
432      else
433        # Otherwise, the file must have been modified
434        puts "Modified: #{path}"
435        name = path
436      end
437
438      # Set up labels for the two files
439      base_label = "#{name} (original)"
440      label = "#{name} (new)"
441
442      # Output a unified diff between the two files
443      puts "=" * 78
444      differ = Svn::Fs::FileDiff.new(@base_root, base_path, @root, path)
445      puts differ.unified(base_label, label)
446      puts
447    end
448  end
449end
450
451# Output usage message and exit
452def usage
453  messages = [
454    "usage: #{$0} REPOS_PATH rev REV [COMMAND] - inspect revision REV",
455    "       #{$0} REPOS_PATH txn TXN [COMMAND] - inspect transaction TXN",
456    "       #{$0} REPOS_PATH [COMMAND] - inspect the youngest revision",
457    "",
458    "REV is a revision number > 0.",
459    "TXN is a transaction name.",
460    "",
461    "If no command is given, the default output (which is the same as",
462    "running the subcommands `info' then `tree') will be printed.",
463    "",
464    "COMMAND can be one of: ",
465    "",
466    "   author:        print author.",
467    "   changed:       print full change summary: all dirs & files changed.",
468    "   date:          print the timestamp (revisions only).",
469    "   diff:          print GNU-style diffs of changed files and props.",
470    "   dirs-changed:  print changed directories.",
471    "   ids:           print the tree, with nodes ids.",
472    "   info:          print the author, data, log_size, and log message.",
473    "   log:           print log message.",
474    "   tree:          print the tree.",
475    "   uuid:          print the repository's UUID (REV and TXN ignored).",
476    "   youngest:      print the youngest revision number (REV and TXN ignored).",
477  ]
478  puts(messages.join("\n"))
479  exit(1)
480end
481
482# Output usage if necessary
483if ARGV.empty?
484  usage
485end
486
487# Process arguments
488path = ARGV.shift
489cmd = ARGV.shift
490rev = nil
491txn = nil
492
493case cmd
494when "rev"
495  rev = Integer(ARGV.shift)
496  cmd = ARGV.shift
497when "txn"
498  txn = ARGV.shift
499  cmd = ARGV.shift
500end
501
502# If no command is specified, use the default
503cmd ||= "default"
504
505# Replace dashes in the command with underscores
506cmd = cmd.gsub(/-/, '_')
507
508# Start SvnLook with the specified command
509SvnLook.new(path, rev, txn).run(cmd)
Note: See TracBrowser for help on using the repository browser.