cvsdelta-1.7.0.orig/0000755000175000017500000000000010044322262014144 5ustar ericeric00000000000000cvsdelta-1.7.0.orig/cvsdelta0000755000175000017500000014555510044322262015716 0ustar ericeric00000000000000#!/bin/sh exec ruby -w -x $0 ${1+"$@"} # -*- ruby -*- #!ruby -w # $Id: cvsdelta,v 1.58 2004/04/30 00:51:46 jeugenepace Exp $ # cvsdelta: summarizes CVS changes and executes the appropriate commands # Returns the home directory, for both Unix and Windows. def home_directory if hm = ENV["HOME"] return hm else hd = ENV["HOMEDRIVE"] hp = ENV["HOMEPATH"] if hd || hp return (hd || "") + (hp || "\\") else return nil end end end # Attribute codes: # 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed # Text color codes: # 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white # Background color codes: # 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white # Attribute codes: # 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed # Text color codes: # 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white # Background color codes: # 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white module ANSIColor @@ATTRIBUTES = Hash[ 'none' => '0', 'reset' => '0', 'bold' => '1', 'underscore' => '4', 'underline' => '4', 'blink' => '5', 'reverse' => '7', 'concealed' => '8', 'black' => '30', 'red' => '31', 'green' => '32', 'yellow' => '33', 'blue' => '34', 'magenta' => '35', 'cyan' => '36', 'white' => '37', 'on_black' => '40', 'on_red' => '41', 'on_green' => '42', 'on_yellow' => '43', 'on_blue' => '44', 'on_magenta' => '45', 'on_cyan' => '46', 'on_white' => '47', ] @@ATTRIBUTES.each do |name, val| eval <<-EODEF def ANSIColor.#{name} "\\e[#{val}m" end EODEF end def ANSIColor.attributes @@ATTRIBUTES.collect { |name, val| name } end # returns the code for the given color string, which is in the format: # [foreground] on [background]. Note that the foreground and background sections # can have modifiers (attributes). Examples: # black # blue on white # bold green on yellow # underscore bold magenta on cyan # underscore red on bold cyan def ANSIColor.code(str) fg, bg = str.split(/\s*\bon_?\s*/) (fg ? foreground(fg) : "") + (bg ? background(bg) : "") end # returns the code for the given background color(s) def ANSIColor.background(bgcolor) make_code("on_" + bgcolor) end # returns the code for the given foreground color(s) def ANSIColor.foreground(fgcolor) make_code(fgcolor) end protected def ANSIColor.make_code(str) if str str.split.collect do |s| if attr = @@ATTRIBUTES[s] "\e[#{attr}m" else $stderr.puts "WARNING: ANSIColor::make_code(" + str + "): unknown color: " + s return "" end end.join("") else "" end end end # Very minimal logging output. If verbose is set, this displays the method and # line number whence called. It can be a mixin to a class, which displays the # class and method from where it called. If not in a class, it displays only the # method. class Log $LOGGING_LEVEL = nil module Severity DEBUG = 0 INFO = 1 WARN = 2 ERROR = 3 FATAL = 4 end include Log::Severity def initialize $LOGGING_LEVEL = @level = FATAL @ignored_files = {} @ignored_methods = {} @ignored_classes = {} @width = 0 @output = $stderr @fmt = "[%s:%04d] {%s}" @autoalign = false @colors = [] @colorize_line = false end def verbose=(v) @level = v ? DEBUG : FATAL end def level=(lvl) @level = lvl end # Assigns output to the given stream. def output=(io) @output = io end # sets whether to colorize the entire line, or just the message. def colorize_line=(col) @colorize_line = col end # Assigns output to a file with the given name. Returns the file; client # is responsible for closing it. def outfile=(f) @output = if f.kind_of?(IO) then f else File.new(f, "w") end end # Creates a printf format for the given widths, for aligning output. def set_widths(file_width, line_width, func_width) @fmt = "[%#{file_width}s:%#{line_width}d] {%#{func_width}s}" end def ignore_file(fname) @ignored_files[fname] = true end def ignore_method(methname) @ignored_methods[methname] = true end def ignore_class(classname) @ignored_classes[classname] = true end def log_file(fname) @ignored_files.delete(fname) end def log_method(methname) @ignored_methods.delete(methname) end def log_class(classname) @ignored_classes.delete(classname) end # Sets auto-align of the {function} section. set_widths will likely # result in nicer output. def autoalign @autoalign = true end def debug(msg = "", depth = 1, &blk) log(msg, DEBUG, depth + 1, &blk) end def info(msg = "", depth = 1, &blk) log(msg, INFO, depth + 1, &blk) end def warn(msg = "", depth = 1, &blk) log(msg, WARN, depth + 1, &blk) end def error(msg = "", depth = 1, &blk) log(msg, ERROR, depth + 1, &blk) end def fatal(msg = "", depth = 1, &blk) log(msg, FATAL, depth + 1, &blk) end # Logs the given message. def log(msg = "", level = DEBUG, depth = 1, cname = nil, &blk) if level >= @level c = caller(depth)[0] c.index(/(.*):(\d+)(?::in \`(.*)\')?/) file, line, func = $1, $2, $3 file.sub!(/.*\//, "") if cname func = cname + "#" + func end if @ignored_files[file] || (cname && @ignored_classes[cname]) || @ignored_methods[func] # skip this one. elsif @autoalign print_autoaligned(file, line, func, msg, level, &blk) else print_formatted(file, line, func, msg, level, &blk) end end end # Shows the current stack. def stack(msg = "", level = DEBUG, depth = 1, cname = nil, &blk) if level >= @level stk = caller(depth) for c in stk # puts "c : #{c}" c.index(/(.*):(\d+)(?::in \`(.*)\')?/) file, line, func = $1, $2, $3 file.sub!(/.*\//, "") func ||= "???" # puts "func : #{func}" # puts "cname: #{cname}" if cname func = cname + "#" + func end if @ignored_files[file] || (cname && @ignored_classes[cname]) || @ignored_methods[func] # skip this one. elsif @autoalign print_autoaligned(file, line, func, msg, level, &blk) else print_formatted(file, line, func, msg, level, &blk) end msg = "" end end end def print_autoaligned(file, line, func, msg, level, &blk) @width = [ @width, func.length ].max hdr = sprintf "[%s:%04d] {%-*s} ", file, line, @width print(hdr, msg, level) end def print_formatted(file, line, func, msg, level, &blk) hdr = sprintf @fmt, file, line, func print(hdr, msg, level, &blk) end def print(hdr, msg, level, &blk) # puts "hdr: #{hdr}, msg #{msg}, level #{level}" if blk # puts "block is given" x = blk.call if x.kind_of?(String) # puts "got a string!" msg = x else # puts "did not get a string" return end else # puts "block not given" end if @colors[level] if @colorize_line @output.puts @colors[level] + hdr + " " + msg.to_s.chomp + ANSIColor.reset else @output.puts hdr + " " + @colors[level] + msg.to_s.chomp + ANSIColor.reset end else @output.puts hdr + " " + msg.to_s.chomp end end def set_color(level, color) # log("#{level}, #{color}") @colors[level] = ANSIColor::code(color) end # by default, class methods delegate to a single app-wide log. @@log = Log.new def Log.verbose=(v) @@log.verbose = v end def Log.level=(lvl) @@log.level = lvl end # Assigns output to the given stream. def Log.output=(io) @@log.output = io end # sets whether to colorize the entire line, or just the message. def Log.colorize_line=(col) @@log.colorize_line = col end # Assigns output to a file with the given name. Returns the file; client # is responsible for closing it. def Log.outfile=(fname) @@log.outfile = fname end # Creates a printf format for the given widths, for aligning output. def Log.set_widths(file_width, line_width, func_width) @@log.set_widths(file_width, line_width, func_width) end def Log.ignore_file(fname) @@log.ignore_file(fname) end def Log.ignore_method(methname) @@ignored_methods[methname] = true end def Log.ignore_class(classname) @@ignored_classes[classname] = true end def Log.log_file(fname) @@log.log_file(fname) end def Log.log_method(methname) @@log.log_method(methname) end def Log.log_class(classname) @@log.log_class(classname) end # Sets auto-align of the {function} section. set_widths will likely # result in nicer output. def Log.autoalign @@log.autoalign end def Log.debug(msg = "", depth = 1, &blk) @@log.log(msg, DEBUG, depth + 1, &blk) end def Log.info(msg = "", depth = 1, &blk) @@log.log(msg, INFO, depth + 1, &blk) end def Log.warn(msg = "", depth = 1, &blk) @@log.log(msg, WARN, depth + 1, &blk) end def Log.error(msg = "", depth = 1, &blk) @@log.log(msg, ERROR, depth + 1, &blk) end def Log.fatal(msg = "", depth = 1, &blk) @@log.log(msg, FATAL, depth + 1, &blk) end # Logs the given message. def Log.log(msg = "", level = DEBUG, depth = 1, cname = nil, &blk) @@log.log(msg, level, depth + 1, cname, &blk) end def Log.set_color(level, color) @@log.set_color(level, color) end def Log.stack(msg = "", level = DEBUG, depth = 1, cname = nil, &blk) @@log.stack(msg, level, depth, cname, &blk) end end class AppLog < Log include Log::Severity end module Loggable # Logs the given message, including the class whence invoked. def log(msg = "", level = Log::DEBUG, depth = 1, &blk) AppLog.log(msg, level, depth + 1, self.class.to_s) end def debug(msg = "", depth = 1, &blk) AppLog.log(msg, Log::DEBUG, depth + 1, self.class.to_s, &blk) end def info(msg = "", depth = 1, &blk) AppLog.log(msg, Log::INFO, depth + 1, self.class.to_s, &blk) end def warn(msg = "", depth = 1, &blk) AppLog.log(msg, Log::WARN, depth + 1, self.class.to_s, &blk) end def error(msg = "", depth = 1, &blk) AppLog.log(msg, Log::ERROR, depth + 1, self.class.to_s, &blk) end def fatal(msg = "", depth = 1, &blk) AppLog.log(msg, Log::FATAL, depth + 1, self.class.to_s, &blk) end def stack(msg = "", level = Log::DEBUG, depth = 1, &blk) AppLog.stack(msg, level, depth + 1, self.class.to_s, &blk) end end class CVSDeltaOptions include Loggable attr_accessor :adds attr_accessor :banner attr_accessor :changes attr_accessor :compression attr_accessor :confirm attr_accessor :deletes attr_accessor :execute attr_accessor :fromdate attr_accessor :fromrev attr_accessor :package attr_accessor :process_unknown_dirs attr_accessor :progress attr_accessor :quiet attr_accessor :tmpdir attr_accessor :todate attr_accessor :torev attr_accessor :verbose attr_accessor :version def initialize(package = "undef", version = "1.2.3.4") @package = package @version = version @verbose = false # whether to spew debugging output @quiet = false # whether to suppress warnings @confirm = false # whether to confirm remove commands @compression = 3 # compression for net transfer @changes = true # whether to show files that changed @adds = true # whether to show files that were added @deletes = true # whether to show files that were deleted @execute = false # whether to execute @banner = true @progress = false @fromdate = nil # starting date of comparison ("cvs -D @fromrev") @todate = nil # ending date of comparison ("cvs -. @from... -D @todate") @fromrev = nil # starting revision of comparison ("cvs -r @fromrev") @torev = nil # ending revision of comparison ("cvs -... @from.... -r @torev") @process_unknown_dirs = true # whether to skip directories that are not in CVS @tmpdir = ENV["TMPDIR"] || "/tmp" end def run read_global_rcfile read_local_rcfile read_environment_variable read_options end def read_environment_variable # process the environment variable if ENV["CVSDELTAOPTS"] options = ENV["CVSDELTAOPTS"].split(/\s+/) while options.length > 0 opt = options.shift log "processing opt " + opt arg = options.shift process_option(opt, options) end end end def read_options while ARGV.length > 0 # can't modify ARGV, as of Ruby 1.8.1: arg = ARGV.shift.dup log "processing #{arg}" if process_option(arg, ARGV) ARGV.unshift(arg) break end end end # Returns whether the value matches a true value, such as "yes", "true", or # "on". def to_boolean(value) [ "yes", "true", "on" ].include?(value.to_s.downcase) end def process_option(opt, args = nil) opt.gsub!(/^\-+/, "") case opt when "a", "adds" @adds = true when "A", "no-adds" @adds = false when "c", "changes" @changes = true when "C", "no-changes", "nodiff" @changes = false @changes = false when "d", "deletes" @deletes = true when "D", "no-deletes" @deletes = false when "e", "execute" @execute = true when "h", "help" show_help exit when "i", "confirm" @confirm = true when "q", "quiet" @quiet = true @progress = false when "s", "skip-unknown-directories" @process_unknown_dirs = false when "V", "verbose" @verbose = true when "v", "version" print @package, ", version ", @version, "\n" print "Written by Jeff Pace (jpace@incava.org).\n" print "Released under the Lesser GNU Public License.\n" exit 1 when "z", "compression" # no need to convert it from an integer, since it'll be written back out as # a string: @compression = args.shift when "f", "from-date" @fromdate = args.shift when "t", "to-date" @todate = args.shift when "F", "from-revision" @fromrev = args.shift when "T", "to-revision" @torev = args.shift when "banner" @banner = true when /^no\-?banner$/ @banner = false when "progress" @progress = true when /^no\-?progress$/ @progress = false when "config" printf "%s: %s\n", "adds", @adds printf "%s: %s\n", "banner", @banner printf "%s: %s\n", "changes", @changes printf "%s: %s\n", "compression", @compression printf "%s: %s\n", "confirm", @confirm printf "%s: %s\n", "deletes", @deletes printf "%s: %s\n", "execute", @execute printf "%s: %s\n", "fromdate", @fromdate printf "%s: %s\n", "fromrev", @fromrev printf "%s: %s\n", "package", @package printf "%s: %s\n", "process_unknown_dirs", @process_unknown_dirs printf "%s: %s\n", "progress", @progress printf "%s: %s\n", "quiet", @quiet printf "%s: %s\n", "tmpdir", @tmpdir printf "%s: %s\n", "todate", @todate printf "%s: %s\n", "torev", @torev printf "%s: %s\n", "verbose", @verbose printf "%s: %s\n", "version", @version printf "%s: %s\n", "ruby version", RUBY_VERSION exit else return true end return false end def read_rc_file(rc) IO.readlines(rc).each do |line| line.sub!(/\s*#.*/, "") line.chomp! name, value = line.split(/\s*[=:]\s*/) next unless name && value case name when "adds" @adds = to_boolean(value) when "skip-unknown-directories" @process_unknown_dirs = !to_boolean(value) when "changes" @changes = to_boolean(value) when "compression" # no need to convert it from an integer, since it'll be written back out # as a string: @compression = value when "confirm" @confirm = to_boolean(value) when "deletes" @deletes = to_boolean(value) when "diff" @changes = !to_boolean(value) when "execute" @execute = to_boolean(value) when "quiet" @quiet = @progress = to_boolean(value) when "verbose" @verbose = to_boolean(value) when "banner" @banner = to_boolean(value) end end end def read_global_rcfile # process the rc file if hd = home_directory rc = hd + "/.cvsdeltarc" log "reading RC file: #{rc}" if File.exists?(rc) read_rc_file(rc) end end end def read_local_rcfile # Use the topmost resource file in this project. We may refine this # functionality so that multiple rc files can be used within a project topdir = find_top_of_project if topdir && File.exists?(topdir + "/.cvsdeltarc") read_rc_file(topdir + "/.cvsdeltarc") end end def find_top_of_project(dir = File.expand_path(".")) repfile = dir + "/CVS/Repository" if File.exists?(repfile) IO.readlines(repfile).each do |line| if line.index("/") # keep going up the directory structure if dir == "/" return nil else return find_top_of_project(File.dirname(dir)) end else return dir end end end return nil end def show_help puts "USAGE" puts " cvsdelta [options] directory..." puts "" puts "OPTIONS" puts " -a, --adds" puts " Display the files that were added." puts "" puts " -A, --no-adds" puts " Do not display the files that were added." puts "" puts " --banner, --no-banner" puts " Whether to display the header and footer." puts "" puts " -c, --changes" puts " Display the files that were changed." puts "" puts " -C, --no-changes, --nodiff" puts " Do not compare files that exist locally and in CVS." puts "" puts " -d, --deletes" puts " Display the files that were deleted." puts "" puts " -D, --no-deletes" puts " Do not display the files that were deleted." puts "" puts " -e, --execute" puts " Execute the associated CVS commands (\"add\" and \"remove\") for " puts " the added and deleted files." puts "" puts " -f, --from-date" puts " Compare the files to their version as of the given date, rather" puts " than their current version in CVS." puts "" puts " -F, --from-revision" puts " Compare the files to their version as of the given revision, rather" puts " than their current version in CVS." puts "" puts " -h, --help" puts " Display a help message." puts "" puts " -i, --confirm" puts " Interactively confirm deleted files with the user before removing them" puts " from CVS." puts "" puts " --progress, --no-progress" puts " Whether to show the progress meter." puts "" puts " -q, --quiet" puts " Run with minimum output." puts "" puts " -s, --skip-unknown-directories" puts " Skip directories that are not in CVS." puts "" puts " -t, --to-date" puts " Compare the files to their version as of the given date, rather" puts " than to the local files. This is valid only with the --from-date" puts " or --from-revision options." puts "" puts " -T, --to-revision" puts " Compare the files to their version as of the given revision, rather" puts " than to the local files. This is valid only with the --from-date" puts " or --from-revision options." puts "" puts " -v, --version" puts " Display the version and exit." puts "" puts " -V, --verbose" puts " Run with maximum output." puts "" puts " -z [LEVEL], ---compression [LEVEL]" puts " Set the compression to the given level for net traffic." end end # cvsdelta: summarizes CVS changes and executes the appropriate commands require 'find' # A primitive "progress meter", for showing when something time-consuming is # being done. If the global variable $verbose is set, then the tick() method # displays the argument passed. If not set, the tick() method displays the # spinning character with each tick. class ProgressMeter def initialize(verbose) @progress = %w{ | \\ - / } @count = 0 @verbose = verbose end def tick(what = "...") if @verbose # what.chomp! # Log.log "processing #{what}" else print "\r" @count = (@count + 1) % 4 print @progress[@count] end end end module FileTester # the percentage of characters that we allow to be odd in a text file ODD_FACTOR = 0.3 # how many bytes (characters) of a file we test TEST_LENGTH = 1024 # extensions associated with files that are always text: KNOWN_TEXT = %w{ txt c cpp mk h hpp html java } # extensions associated with files that are never text: KNOWN_NONTEXT = %w{ a o obj class elc gif gz jar jpg jpeg png pdf tar Z } # returns if the given file is nothing but text (ASCII). def FileTester.text?(file) # Don't waste our time if it doesn't even exist: return false unless File.exists?(file) if file.index(/\.(\w+)\s*$/) suffix = $1 return true if KNOWN_TEXT.include?(suffix) return false if KNOWN_NONTEXT.include?(suffix) end ntested = 0 nodd = 0 f = File.new(file) f.each do |line| # split returns strings, whereas we want characters (bytes) chars = line.split(//, TEST_LENGTH).collect { |w| w[0] } # using the limit parameter to split results in the last character being # "0" (nil), so remove it if chars.size > 1 and chars[-1].to_i == 0 chars = chars[0 .. -2] end chars.each do |ch| ntested += 1 # never allow null in a text file return false if ch.to_i == 0 nodd += 1 unless FileTester.ascii?(ch) return FileTester.summary(nodd, ntested) if ntested >= TEST_LENGTH end end return FileTester.summary(nodd, ntested) end def FileTester.summary(nodd, ntested) return nodd < ntested * ODD_FACTOR end # returns if the given character is ASCII. def FileTester.ascii?(c) # from ctype.h return (c.to_i & ~0x7f) == 0 end end # Additions to the File built-in class. class File # Returns a File::Stat object, or null if there were errors (such as the file # not existing, access denied, etc.). def File.status(fd) begin return File.stat(fd) rescue # ignore files that could not be read, etc. return nil end end # Returns whether the given object is a file. Ignores errors. def File.is_file?(fd) fs = File.status(fd) return fs && fs.file? end # Returns whether the given object is a directory. Ignores errors. def File.is_directory?(fd) fs = File.status(fd) return fs && fs.directory? end # Returns an array containing each of the names for which the associated block # returned true. def File.find_where(dir) names = Array.new Find.find(dir) do |f| names.push(f) if yield(f) end names end # Returns an array of all files under the given directory. def File.find_files(dir) File.find_where(dir) { |f| is_file?(f) } end # Returns an array of all directory under the given directory. def File.find_directories(dir) File.find_where(dir) { |f| is_directory?(f) } end # Returns an array of all files within the given directory. def File.local_files(dir) Dir.new(dir).find_all { |f| is_file?(f) } end # Strips the PWD and the leading ./ def File.clean_name(fname) file = fname.dup file.gsub!(Dir.pwd, "") file.gsub!(/^\//, "") file.sub!(/^\.\//, "") file end def File.is_text?(fname) FileTester.text?(fname) end end # A hash that ensures that we use file name of the form: "foo/Bar", not # "./foo/Bar". class FileHash < Hash def []=(f, value) fname = File.clean_name(f) super(fname, value) end def [](f) fname = File.clean_name(f) super(fname) end end # An array that ensures that we use file name of the form: "foo/Bar", not # "./foo/Bar". class FileArray < Array def []=(index, name) file = File.clean_name(f) super(index, name) end def push(name) fname = File.clean_name(name) super(fname) end end # Directories listed so that the parents are first in the list. class OrderedDirectoryList < Array def initialize(files) files.each { |f| add(File.dirname(f)) } end # add a directory def add(dir) if dir && !File.exists?(dir + "/CVS/Entries") # TODO: remove the CVS-icity of this: # attempt to add the parent, unless this is "." # note: this won't work if dir == "." if dir == "." puts "ERROR: Cannot process files from within a directory" puts "not in CVS. Please move up to the parent directory" puts "and retry." exit end add(File.dirname(dir)) pos = index(dir) if pos # nothing to do; dir is already in the list else pdpos = index(File.dirname(dir)) if pdpos # parent already in the list, so insert this dir immediately afterward self[pdpos + 1, 0] = dir else # prepending unshift(dir) end end end end end # Extended so that we can convert "Unix" (shell, actually) regular expressions # ("*.java") to Ruby regular expressions ("/\.java$/"). class Regexp # shell expressions to Ruby regular expressions @@sh2re = Hash[ '*' => '.*', '?' => '.', '[' => '[', ']' => ']', '.' => '\.', '$' => '\$', '/' => '\/' ] # Returns a regular expression for the given Unix file system expression. def Regexp.unixre_to_string(pat) str = pat.gsub(/(\\.)|(.)/) do if $1 $1 else if @@sh2re.has_key?($2) then @@sh2re[$2] else $2 end end end str end end # Represents .cvsignore files. class IgnoredPatterns < Hash include Loggable def initialize @ignorename = ".cvsignore" @dirsread = Array.new end def read(dir) # from the CVS default settings -- ignoring overrides log dir return if @dirsread.include?(dir) @dirsread.push(dir) log "reading #{dir}" pats = %w{ CVS *~ .cvsignore *.o *$ *.BAK *.Z *.a *.bak *.elc *.exe *.ln *.obj *.olb *.old *.orig *.rej *.so . .. .del-* .make.state .nse_depinfo CVS.adm RCS RCSLOG SCCS TAGS _$* core cvslog.* tags } # can't put these guys in the qw() list: ['.#*', '#*', ',*'].each { |p| pats.push(p) } # read the repository-wide cvsignore file, if it exists and is local. #cvsroot = ENV["CVSROOT"] #if cvsroot # cri = cvsroot + "/CVSROOT/cvsignore" # repo = read_ignore_file_named(cri) # pats.push(*repo) unless repo.length == 0 #end # read ~/ homedir = ENV["HOME"] # unix unless homedir # windows homedir = ENV["HOMEDRIVE"] homepath = ENV["HOMEPATH"] if homepath then if homedir then homedir += homepath else homedir = homepath end end end global = read_ignore_file(homedir) pats.push(*global) unless global.length == 0 # read in the current directory local = read_ignore_file(dir) pats.push(*local) unless local.length == 0 # prepend the current directory to the patterns, contending with the fact # that the directory might actually be a valid regular expression. # wildcard if the pattern is a directory pats = pats.collect do |p| p += "/*" if File.directory?(dir + "/" + p) p end # make a regular expression for each one, to be the entire string (^...$) qdir = Regexp.quote(dir) self[dir] = pats.collect do |p| p = qdir + "/" + Regexp.unixre_to_string(p) re = Regexp.new("^" + p + "$") log "storing ignored pattern re " + re.source + ", dir " + dir re end end # Reads the ignore file from the given directory, using the default ignore # name. def read_ignore_file(dir) pats = Array.new if dir cifile = dir + "/" + @ignorename if File.exists?(cifile) log "reading #{cifile}" IO.foreach(cifile) do |line| line.chomp! line.gsub!(/\+/, '\\+') pats.push(*line.split) if line.split.size > 0 end else log "no ignore file in " + dir end end log "patterns: #{pats}" pats end # Reads the given ignore file, if it exists. def read_ignore_file_named(fname) pats = Array.new if File.exists?(fname) IO.foreach(fname) do |line| line.chomp! line.gsub!(/\+/, '\\+') pats.push(line) end else log "no such file: " + fname end pats end # Returns if the file is ignored. Checks the name as both "./name" and "name". def is_ignored?(name) # log "name = #{name}" if name.index("./") == 0 withpref, nopref = name, name.sub!("./", "") else withpref, nopref = "./" + name, name end [ withpref, nopref ].each do |name| dir = name # log "dirs = " + keys.join(", ") while dir = File.dirname(dir) if include?(dir) regexps = self[dir] regexps.each do |re| # log "matching " + name + " against " + re.source # stop as soon as we find out it is ignored return true if re.match(name) end else # log "dir " + dir + " is not included" end break if dir == "." # else we'll cycle continuously end end return false # it's not ignored end end # A file that has changed with respect to the configuration management system. # This can be one that has been added (a new file), changed (a previously # # existing file), or deleted (one that has been removed). class DeltaFile include Loggable attr_accessor :adds, :changes, :deletes, :name UNKNOWN = -1 BINARY = -2 def initialize(name) # in Ruby, these are primitives, not Objects, so they are not # referencing the same primitive value (i.e., this is just like Java) @adds = @changes = @deletes = 0 @name = File.clean_name(name) end def total @adds + @changes + @deletes end def is_counted? [ @adds, @changes, @deletes ].each do |v| if [ UNKNOWN, BINARY ].grep(v).length > 0 return false end end return true end def print(nm = nil) nm = symbol + " " + name unless nm [total, adds, changes, deletes].each do |v| str = case v when UNKNOWN "???" when BINARY "bin" else v.to_s end printf("%7s ", str) end $stdout.print nm, "\n" end def to_s self.class.to_s + " \"#{@name}\" a:#{@adds} c:#{@changes} d:#{deletes}" end end class ExistingFile < DeltaFile def symbol; "*"; end end class DeletedFile < DeltaFile def initialize(name) super # it would be nice to know how long the file was, i.e., many lines were # deleted @deletes = UNKNOWN end def symbol; "-"; end end class NewFile < DeltaFile def NewFile.create(fname) File.is_text?(fname) ? NewFile.new(fname) : NewBinaryFile.new(fname) end def initialize(name) super @adds = IO.readlines(name).length end def symbol; "+"; end end class NewBinaryFile < DeltaFile def initialize(name) super f = File.new(name) # @adds = f.read.length # @adds = f.readlines.length @adds = BINARY end def symbol; "+"; end # def print(nm = nil) # nm = symbol + " " + name unless nm # [total, adds, changes, deletes].each do |v| # printf("%7sb ", v == @@UNKNOWN ? "???" : v.to_s) # end # $stdout.print nm, "\n" # end end class AddedUncountedFile < DeltaFile def initialize(name) super @adds = UNKNOWN end def symbol; "+"; end end # Processes output from "cvs diff". class DiffOutput include Loggable def initialize(total) fmt = '(\d+)(?:,(\d+))?' @total = total @regexp = Regexp.new("^" + fmt + char + fmt) @md = nil end def match(line) @md = @regexp.match(line) end def update(record) nlines = number_of_lines update_record(record, nlines) update_record(@total, nlines) end def to_s self.class.to_s + " " + @regexp.source end # Returns the amount of lines that changed, based on the MatchData object # which is from standard diff output def number_of_lines from = diff_difference(1, 2) to = diff_difference(3, 4) 1 + [from, to].max end # Returns the difference between the two match data objects, which represent # diff output (3,4c4). def diff_difference(from, to) if @md[to] then @md[to].to_i - @md[from].to_i else 0 end end end class DiffOutputAdd < DiffOutput def char; 'a'; end def update_record(rec, nlines) rec.adds += nlines end end class DiffOutputChange < DiffOutput def char; 'c'; end def update_record(rec, nlines) rec.changes += nlines end end class DiffOutputDelete < DiffOutput def char; 'd'; end def update_record(rec, nlines) rec.deletes += nlines end end class CVSDiff include Loggable attr_accessor :tmpdir, :fromdate, :fromrev, :todate, :torev, :compression, :progress, :total attr_reader :missing, :deleted, :changed def initialize(args, verbose = nil) name = self.class @total = DeltaFile.new("total") @addre = DiffOutputAdd.new(@total) @delre = DiffOutputDelete.new(@total) @chgre = DiffOutputChange.new(@total) @tmpdir = "/tmp" @fromdate = nil @fromrev = nil @todate = nil @torev = nil @compression = 3 @progress = nil @verbose = verbose @args = args end def run # Ignore the .cvsrc file; handle only normal diff output. # Tweaking compression (via -z[0 .. 9]) makes diff less likely to hang # after producing output. Both -z0 and -z9 work best on my system (against # the doctorj CVS repository at SourceForge.net). curfile = nil diffopts = "" if @fromrev || @fromdate if @fromrev diffopts += " -r #{@fromrev} " else diffopts += " -D \"#{@fromdate}\" " end if @torev || @todate if @torev diffopts += " -r #{@torev} " else diffopts += " -D \"#{@todate}\" " end end elsif @torev || @todate $stderr.puts "ERROR: --to-... option requires --from-..." exit end diffopts += @args.join(" ") @missing = FileArray.new @deleted = FileArray.new @changed = FileHash.new # backticks seem to work more consistenty than IO.popen, which was losing # lines from the CVS diff output. cmd = "cvs -fq -z" + @compression.to_s + " diff " + diffopts + " 2>&1" log "executing command " + cmd # For whatever reason, between CVS and Ruby, the best combination is to # write CVS's output to a temporary file. Trying to tie to the output # streams was resulting in some output being lost, especially that written # to stderr. pid = Process.pid outfile = @tmpdir + "/cvsdelta." + pid.to_s trap("INT") do # If we get interrupted, make sure we delete the outfile: File.unlink(outfile) if File.exists?(outfile) # This bypasses the stack trace on exit. abort end cmd = "(" + cmd + ") > " + outfile `#{cmd}` lines = IO.readlines(outfile) if lines.size > 0 && lines[0].index(/cvs \[diff aborted\]: Can\'t parse date\/time: (.+)/) $stderr.puts "ERROR: invalid date/time: '#{$1}'" exit end lines.each do |line| @progress.tick(line) if @progress log "line: " + line if line.index(/^\?\s*(\S*)/) # some CVS servers seem to write new files as "? foo/bar.x", but we'll # figure out the new files for ourselves anyway elsif line.index(/^cvs server:\s*(\S*)was removed/) || line.index(/^cvs (?:diff|server): *cannot find\s*(\S*)/) # various ways that CVS servers tell us what was removed, but we'll # figure it out for ourself file = $1 # add_deleted_file(file) log "deleted file: " + file elsif line.index(/^Index:\s+(\S+)/) curfile = $1 log "new current file: #{curfile}" elsif line.index(/^\s*cvs (?:diff|server): no revision for .+ in file (.*)/) fname = $1 log "missing file line: #{line}" log "adding missing file: #{fname}" @missing.push(fname) elsif line.index(/^\s*cvs server: tag .*? is not in file (.*)/) fname = $1 log "missing file line: #{line}" log "adding missing file: #{fname}" @missing.push(fname) elsif line.index(/^\s*cvs server: (.*) no longer exists/) fname = $1 log "deleted file #{fname} from line: #{line}" @deleted.push(fname) elsif line.index(/^Binary files .*? and .*? differ/) log "binary files differ" rec = get_record(curfile) rec.changes = DeltaFile::BINARY else [ @addre, @chgre, @delre ].each do |re| if re.match(line) rec = get_record(curfile) re.update(rec) break else # log re.to_s + ": not a match line: " + line.chomp end end end end log "unlinking #{outfile}" File.unlink(outfile) end def dump puts "missing:" @missing.each do |f| puts " #{f}" end puts "deleted:" @deleted.each do |f| puts " #{f}" end puts "changed:" @changed.each do |fname, record| puts " #{record}" end end def get_record(file) unless @changed.include?(file) @changed[file] = ExistingFile.new(file) end @changed[file] end end # CVS requires that added directories and files be done in their own set. That # is, it is invalid to give CVS the command "cvs add foo foo/bar.txt". That must # be done as two separate commands. class CVSEntry include Loggable attr_accessor :added, :parent, :name, :setnumber, :incvs @@entries = Hash.new def CVSEntry.entries @@entries end def initialize(name) @name = name @incvs = File.exists?(name + "/CVS/Entries") @added = false @@entries[name] = self @parent = nil if (fd = File.dirname(name)) && fd != "." if @@entries.has_key?(fd) @parent = @@entries[fd] else @parent = CVSEntry.new(fd) end end end def to_s @name end # Adds to the given list, if it can. Returns whether successful. def add(addset) log "(#{@name}, #{addset})" if @parent if @parent.incvs @setnumber = 0 else unless @parent.added log "adding parent" @parent.add(addset) end @setnumber = @parent.setnumber + 1 end else log "adding orphan to set 0" @setnumber = 0 end addset.add(self, @setnumber) @added = true end end class CVSAddSet include Loggable attr_reader :addlists def initialize(files) entries = files.collect { |f| CVSEntry.new(f) } @addlists = Array.new setnum = 0 while entries.size > 0 curlist = Array.new entries.each do |e| if e.add(self) entries.delete(e) end end setnum += 1 end end def add(entry, setnum) log "(#{entry}, #{setnum})" unless al = @addlists[setnum] al = @addlists[setnum] = Array.new end al.push(entry) unless al.include?(entry) end end # Executes CVS commands. class CVSExecute include Loggable attr_accessor :confirm, :diagnose def initialize(added, deleted) @added = if added.kind_of?(Hash) then added.keys else added end @deleted = if deleted.kind_of?(Hash) then deleted.keys else deleted end @confirm = false @diagnose = false end def run print "\nEXECUTING COMMANDS\n" print "\n ADDs\n" if @added.length > 0 addset = CVSAddSet.new(@added) addset.addlists.each do |list| names = list.collect { |entry| entry.name } execute_command(names, "add") end end if @confirm dels = @deleted.reject do |name| print "delete " + name + "? " ans = $stdin.readline ans.upcase[0, 1] != 'Y' end else dels = @deleted end print "\n DELETEs\n" execute_command(dels, "remove") end def execute_command(names, command) if names.size > 0 # Quote file names, so that spaces are essentially escaped. # TODO: Handle file names that have doublequotes in them. cmd = "cvs " + command + ' ' + names.collect { |n| '"' + n + '"' }.join(" ") print " ", cmd, "\n" unless diagnose system(cmd) end else log "no files to " + command end end end class String # returns if the given string is ASCII. def is_ascii? each do |ch| # from ctype.h if (ch.to_i & ~0x7f) != 0 return false end end return true end end # A difference within a configuration management system. class CVSDelta include Loggable attr_reader :added, :changed, :deleted, :total def initialize(options, args) @options = options # for showing that we're actually doing something @progress = if @options.progress then ProgressMeter.new(@options.verbose) else nil end @ignored_patterns = IgnoredPatterns.new log "args: #{args}" args = [ "." ] unless args.length > 0 @unreadable = Hash.new @entries = Array.new @entfiles = Array.new @args = args end def run if @options.changes read_changes(@args) else log "not processing changes" @added = FileHash.new @changed = FileHash.new @deleted = FileHash.new @total = DeltaFile.new("total") end # determine new files if @options.adds || @options.deletes read_ignored_directories(@args) read_added_deleted_files(@args) else log "not processing adds or deletes" end end def read_ignored_directories(dirs) dirs = @args.collect { |a| File.dirname(a) }.uniq dirs.each do |dir| @ignored_patterns.read(dir) end end def read_ignored_directory(dirs) dirs = @args.collect { |a| File.dirname(a) }.uniq # dirs << cvsed_parent_directories(dirs) # dirs = @args.collect { |a| File.dirname(a) }.uniq # dirs << cvsed_parent_directories(dirs) dirs.each do |dir| @ignored_patterns.read(dir) end exit end def read_changes(args) diff = CVSDiff.new(args) diff.fromdate = @options.fromdate diff.fromrev = @options.fromrev diff.todate = @options.todate diff.torev = @options.torev diff.tmpdir = @options.tmpdir diff.run @added = FileHash.new @changed = diff.changed @deleted = FileHash.new @total = diff.total # If it is only from, we can just get the line count from the local files. # If there is a to, we have to use that date as the update time. # And we have to deal with binary files. # Unfortunately, cvs spews out "update -p" without anything to indicate the # start or end of files. So we can get the total line count correctly, but # not for the individual files. Thus, they'll just get "???" for their line # counts, but the total will be right. And that's probably what most people # are interested in, so that's OK. if diff.missing.size > 0 if @options.torev || @options.todate cmd = "cvs -fq -z" + @options.compression.to_s + " update -p " if @options.torev cmd += " -r #{@options.torev} " else cmd += " -D \"#{@options.todate}\" " end diff.missing.each do |mf| cmd += " \"#{mf}\"" end log "running #{cmd}" lines = `#{cmd}` begin linecount = lines.select { |line| line.is_ascii? }.size rescue # this might have been a file that had not been properly # registered as a binary file. linecount = 1 end diff.missing.each do |mf| fname = File.clean_name(mf) log "#{mf} => #{fname}" @added[fname] = AddedUncountedFile.new(fname) end log "incrementing total adds by #{linecount}" diff.total.adds += linecount else diff.missing.each do |mf| fname = File.clean_name(mf) log "#{mf} => #{fname}" add_file(fname) end end end if diff.deleted.size > 0 diff.deleted.each do |mf| fname = File.clean_name(mf) log "#{mf} => #{fname}" @deleted[fname] = DeletedFile.new(fname) end end end def read_added_deleted_files(args) log "cvsdelta: read_added_deleted_files(" + args.join(", ") + ")" args.each do |arg| if File.directory?(arg) if @options.process_unknown_dirs read_each_subdirectory(arg) else read_each_cvsed_subdirectory(arg) end else # read the CVS/Entries file in the directory of the file dir = File.dirname(arg) read_entries_file(dir) unless dir.index(/\bCVS\b/) log "adding file #{arg}" consider_file_for_addition(arg) end end end def read_each_subdirectory(top) # log top File.find_directories(top).each do |dir| log "processing dir #{dir}" unless dir.index(/\bCVS\b/) read_entries_file(dir) end end File.find_files(top).each do |f| # log "adding found file: #{f}" consider_file_for_addition(f) end end def read_each_cvsed_subdirectory(top) log top cvsdirs = File.find_where(top) do |fd| File.is_directory?(fd) && File.exists?(fd + "/CVS/Entries") end cvsdirs.each do |dir| log "processing dir #{dir}" read_entries_file(dir) File.local_files(dir).each do |f| log "adding local file: #{f}" consider_file_for_addition(f) end end end def read_entries_file(dir) entfile = dir + "/CVS/Entries" if @entfiles.include?(entfile) log "entries file " + entfile + " already read" elsif !File.exists?(entfile) log "no entries file: " + entfile else log "reading entries file: " + entfile IO.foreach(entfile) do |line| @progress.tick(dir) if @progress file, ver, date = line.split('/')[1 .. 3] log "file: #{file}; ver: #{ver}; date: #{date}" if file and ver and !file.empty? and !ver.empty? fullname = File.clean_name(dir + "/" + file) log "adding entry: " + fullname @entries.push(fullname) if date == "dummy timestamp" || date.index("Initial added") if File.exists?(fullname) log "adding new file as changed" @changed[fullname] = NewFile.create(fullname) @total.adds += @changed[fullname].adds else log "adding removed file as changed" @changed[fullname] = DeletedFile.new(fullname) end elsif not File.exists?(fullname) log "entry " + fullname + " is missing" add_deleted_file(fullname) end end end @entfiles.push(entfile) @ignored_patterns.read(dir) end end def add_file(fname) log "file name = #{fname}" if File.readable?(fname) unless @added.include?(fname) # don't add it twice @added[fname] = NewFile.create(fname) if @added[fname].is_counted? log "adding #{fname}: #adds: #{@added[fname].adds}" @total.adds += @added[fname].adds else log "adding #{fname}: not counted" end end elsif !@unreadable.has_key?(fname) puts "\rnot readable: " + fname @unreadable[fname] = true end end def file_included?(fname) @entries.include?(fname) end def file_ignored?(fname) @ignored_patterns.is_ignored?(fname) end def consider_file_for_addition(file) if @options.adds fname = File.clean_name(file) unless file_included?(fname) || file_ignored?(fname) add_file(fname) end else log "not adding new file " + file end end def add_deleted_file(file) if @options.deletes @deleted[file] = DeletedFile.new(file) else log "not adding deleted file " + file end end def print_change_summary if @options.banner puts printf "%-7s %-7s %-7s %-7s %s\n", "total", "added", "changed", "deleted", "file" printf "======= ======= ======= ======= ====================\n" end files = Hash.new [ added, changed, deleted ].each do |ary| ary.each do |file, record| files[file] = record end end files.sort.each { |file, record| record.print } if @options.banner printf "------- ------- ------- ------- --------------------\n"; end total.print("Total") end end $stdout.sync = true # unbuffer output $stderr.sync = true # unbuffer output $stdin.sync = true # unbuffer input $PACKAGE = "cvsdelta" $VERSION = "1.7.0" begin Log.set_widths(15, -5, -35) # Log.verbose = true options = CVSDeltaOptions.new($PACKAGE, $VERSION) options.run Log.verbose = options.verbose # Log.output = "/tmp/cvsdelta.log." + Process.pid.to_s # we should be running this from a CVS'ed directory unless File.exists?("CVS") $stderr.print "this directory does not appear to be part of a CVS project\n" end delta = CVSDelta.new(options, ARGV) delta.run delta.print_change_summary if options.execute exec = CVSExecute.new(delta.added, delta.deleted) exec.confirm = options.confirm exec.run end rescue => e # show only the message, not the stack trace: $stderr.puts "error: #{e}" # puts e.backtrace end cvsdelta-1.7.0.orig/Makefile0000644000175000017500000000243510044322262015610 0ustar ericeric00000000000000# Makefile for glark # Borrowed/copied from ESR. PACKAGE=cvsdelta VERSION=1.7.0 prefix=/usr mandir=$(prefix)/share/man bindir=$(prefix)/bin DOCS = $(PACKAGE).1 $(PACKAGE).pod SOURCES = $(PACKAGE) Makefile $(DOCS) $(PACKAGE).spec all: install: $(PACKAGE).1 mkdir -p $(DESTDIR)$(bindir) mkdir -p $(DESTDIR)$(mandir)/man1 cp -p $(PACKAGE) $(DESTDIR)$(bindir) # gzipping the man page conflicts with Mandrake, which bzips it: # gzip <$(PACKAGE).1 >$(DESTDIR)$(mandir)/man1/$(PACKAGE).1.gz cp -p $(PACKAGE).1 $(DESTDIR)$(mandir)/man1/$(PACKAGE).1 uninstall: rm -f $(DESTDIR)$(bindir)/$(PACKAGE) -rm -f $(DESTDIR)$(mandir)/man1/$(PACKAGE).1* -rm -f $(DESTDIR)$(prefix)/man/man1/glark.1* # Builds the man page from the POD-format documentation. $(PACKAGE).1: $(PACKAGE).pod pod2man --release "$(PACKAGE) $(VERSION)" \ --center "$(PACKAGE) $(VERSION)" \ $^ > $@ $(PACKAGE)-$(VERSION).tar.gz: $(SOURCES) @mkdir $(PACKAGE)-$(VERSION) @cp $(SOURCES) $(PACKAGE)-$(VERSION) @tar -czf $(PACKAGE)-$(VERSION).tar.gz $(PACKAGE)-$(VERSION) @rm -fr $(PACKAGE)-$(VERSION) dist: $(PACKAGE)-$(VERSION).tar.gz # The following rules are not for public use. # Builds the HTML version of the man page. doc/www/$(PACKAGE).html: $(PACKAGE).pod pod2html --noindex --verbose --css=$(PACKAGE)man.css $^ > $@ cvsdelta-1.7.0.orig/cvsdelta.10000644000175000017500000002561010044322262016037 0ustar ericeric00000000000000.\" Automatically generated by Pod::Man v1.34, Pod::Parser v1.13 .\" .\" Standard preamble: .\" ======================================================================== .de Sh \" Subsection heading .br .if t .Sp .ne 5 .PP \fB\\$1\fR .PP .. .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. | will give a .\" real vertical bar. \*(C+ will give a nicer C++. Capital omega is used to .\" do unbreakable dashes and therefore won't be available. \*(C` and \*(C' .\" expand to `' in nroff, nothing in troff, for use with C<>. .tr \(*W-|\(bv\*(Tr .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` "" . ds C' "" 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' 'br\} .\" .\" If the F register is turned on, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . nr % 0 . rr F .\} .\" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .hy 0 .if n .na .\" .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). .\" Fear. Run. Save yourself. No user-serviceable parts. . \" fudge factors for nroff and troff .if n \{\ . ds #H 0 . ds #V .8m . ds #F .3m . ds #[ \f1 . ds #] \fP .\} .if t \{\ . ds #H ((1u-(\\\\n(.fu%2u))*.13m) . ds #V .6m . ds #F 0 . ds #[ \& . ds #] \& .\} . \" simple accents for nroff and troff .if n \{\ . ds ' \& . ds ` \& . ds ^ \& . ds , \& . ds ~ ~ . ds / .\} .if t \{\ . ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" . ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' . ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' . ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' . ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' . ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' .\} . \" troff and (daisy-wheel) nroff accents .ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' .ds 8 \h'\*(#H'\(*b\h'-\*(#H' .ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] .ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' .ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' .ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] .ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] .ds ae a\h'-(\w'a'u*4/10)'e .ds Ae A\h'-(\w'A'u*4/10)'E . \" corrections for vroff .if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' .if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' . \" for low resolution devices (crt and lpr) .if \n(.H>23 .if \n(.V>19 \ \{\ . ds : e . ds 8 ss . ds o a . ds d- d\h'-1'\(ga . ds D- D\h'-1'\(hy . ds th \o'bp' . ds Th \o'LP' . ds ae ae . ds Ae AE .\} .rm #[ #] #H #V #F C .\" ======================================================================== .\" .IX Title "CVSDELTA 1" .TH CVSDELTA 1 "2004-04-08" "cvsdelta 1.7.0" "cvsdelta 1.7.0" .SH "NAME" cvsdelta \- Manage and summarize the differences between a CVS project and local files. .SH "SYNOPSIS" .IX Header "SYNOPSIS" \&\fBcvsdelta\fR [options] [file ...] .SH "DESCRIPTION" .IX Header "DESCRIPTION" Manages and summarizes the differences between a \s-1CVS\s0 project and local files, showing the changes of the files themselves and of their contents. Added, changed, and deleted files are denoted with `+', `*', and `\-' respectively. The number of lines are displayed. .SH "OPTIONS" .IX Header "OPTIONS" .IP "\fB\-a\fR, \fB\-\-adds\fR" 4 .IX Item "-a, --adds" Display the files that were added. This is done by default. .IP "\fB\-A\fR, \fB\-\-no\-adds\fR" 4 .IX Item "-A, --no-adds" Do not display the files that were added. The default is to show the added files. .IP "\fB\-\-banner\fR, \fB\-\-no\-banner\fR" 4 .IX Item "--banner, --no-banner" Whether to show the banner, i.e., the header and footer of the output table. By default, the banner is displayed. .IP "\fB\-c\fR, \fB\-\-changes\fR" 4 .IX Item "-c, --changes" Display the files that were changed. This is done by default. .IP "\fB\-C\fR, \fB\-\-no\-changes\fR, \fB\-\-nodiff\fR" 4 .IX Item "-C, --no-changes, --nodiff" Do not compare files that exist locally and in \s-1CVS\s0. This executes significantly faster, and is useful for when one only wants to add and remove files, without getting the summary of the changes. It is also helpful for dealing with situations where \s-1CVS\s0 seems to hang after producing diff output, although changing the compression level can help with this. The default is to show the changed files. .IP "\fB\-d\fR, \fB\-\-deletes\fR" 4 .IX Item "-d, --deletes" Display the files that were deleted. This is done by default. .IP "\fB\-D\fR, \fB\-\-no\-deletes\fR" 4 .IX Item "-D, --no-deletes" Do not display the files that were deleted. The default is to show the files that were deleted. .IP "\fB\-e\fR, \fB\-\-execute\fR" 4 .IX Item "-e, --execute" Execute the associated \s-1CVS\s0 commands (\*(L"add\*(R" and \*(L"remove\*(R") for the added and deleted files. .IP "\fB\-f \s-1DATE\s0\fR, \fB\-\-from\-date \s-1DATE\s0\fR" 4 .IX Item "-f DATE, --from-date DATE" Compare the files to their version as of the given date, rather than their current version in \s-1CVS\s0. The date is expected to be in \s-1CVS\s0 format, for example, 2002/12/31. .IP "\fB\-F \s-1REVISION\s0\fR, \fB\-\-from\-revision \s-1REVISION\s0\fR" 4 .IX Item "-F REVISION, --from-revision REVISION" Compare the files to their version as of the given revision, rather than their current version in \s-1CVS\s0. .IP "\fB\-h\fR, \fB\-\-help\fR" 4 .IX Item "-h, --help" Display a help message. .IP "\fB\-i\fR, \fB\-\-confirm\fR" 4 .IX Item "-i, --confirm" Interactively confirm deleted files with the user before removing them from \s-1CVS\s0. The default is simply to execute the remove command for relevant files. This option is valid only with the \-\-execute option. .IP "\fB\-\-progress\fR, \fB\-\-no\-progress\fR" 4 .IX Item "--progress, --no-progress" Whether to show the progress meter as the program is working with \s-1CVS\s0. By default, this is not shown. .IP "\fB\-q\fR, \fB\-\-quiet\fR" 4 .IX Item "-q, --quiet" Run with minimum output. In quiet mode, the progress meter is not displayed. .IP "\fB\-s\fR, \fB\-\-skip\-unknown\-directories\fR" 4 .IX Item "-s, --skip-unknown-directories" Skip directories that are not in \s-1CVS\s0. The default behavior is to process all subdirectories. .IP "\fB\-t \s-1DATE\s0\fR, \fB\-\-to\-date \s-1DATE\s0\fR" 4 .IX Item "-t DATE, --to-date DATE" Compare the files to their version as of the given date, rather than to the local files. The date is expected to be in \s-1CVS\s0 format, for example, 2001/08/21. This option is valid only with the \-\-from\-date or \-\-from\-revision options. .IP "\fB\-T \s-1REVISION\s0\fR, \fB\-\-to\-revision \s-1REVISION\s0\fR" 4 .IX Item "-T REVISION, --to-revision REVISION" Compare the files to their version as of the given revision, rather than to the local files. This is valid only with the \-\-from\-date or \-\-from\-revision options. .IP "\fB\-v\fR, \fB\-\-version\fR" 4 .IX Item "-v, --version" Display the version and exit. .IP "\fB\-V\fR, \fB\-\-verbose\fR" 4 .IX Item "-V, --verbose" Run with maximum output, which is written to standard error. .IP "\fB\-z [\s-1LEVEL\s0]\fR, \-\fB\-\-compression [\s-1LEVEL\s0]\fR" 4 .IX Item "-z [LEVEL], ---compression [LEVEL]" Set the compression to the given level for net traffic. This can be useful if the \s-1CVS\s0 server seems to hang. The default level is 3; 0 and 9 work well. .SH "ARGUMENTS" .IX Header "ARGUMENTS" Arguments may be either files or directories. .IP "\fBFile\fR" 4 .IX Item "File" If a file is provided, it is compared against the equivalent in \s-1CVS\s0, if any. The \&.cvsignore files are applied, so that although the file was explicitly provided, it may be ignored. .IP "\fBDirectory\fR" 4 .IX Item "Directory" Directories are processed recursively for files that are not filtered out by \&.cvsignore files. .SH "EXAMPLES" .IX Header "EXAMPLES" .Vb 1 \& % cvsdelta .Ve .PP Lists the changed files. .PP .Vb 1 \& % cvsdelta --execute .Ve .PP Lists the changed files, and executes the associated add and remove commands. .PP .Vb 1 \& % cvsdelta --compression 9 --execute --confirm --no-changes .Ve .PP Run with the maximum compression level. Add and remove the appropriate files, but get confirmation from the user before removing any files. Do not compare files that exist both locally and in \s-1CVS\s0. .SH "ENVIRONMENT" .IX Header "ENVIRONMENT" .IP "$HOME/.cvsdeltarc, .../project/.cvsdeltarc" 4 .IX Item "$HOME/.cvsdeltarc, .../project/.cvsdeltarc" Resource files containing name/value pairs, separated by either ':' or '='. The valid fields of a .cvsdeltarc file are as follows, with their default values: .Sp .Vb 11 \& quiet: false \& verbose: false \& execute: false \& confirm: false \& diff: true \& compression: 3 \& adds: true \& changes: true \& deletes: true \& skip-unknown-directories: false \& banner: true .Ve .Sp \&\*(L"yes\*(R" and \*(L"on\*(R" are synonymnous with \*(L"true\*(R". \*(L"no\*(R" and \*(L"off\*(R" signify \*(L"false\*(R". .Sp The values in the project .cvsdeltarc file will override those in the user's \&.cvsdeltarc file. The project .cvsdeltarc is assumed to be at the topmost directory in the local file hierarchy. .IP "\s-1CVSDELTAOPTS\s0" 4 .IX Item "CVSDELTAOPTS" A string of whitespace-delimited options, the same as listed above. These values override those of the resource files. .SH "NOTES" .IX Header "NOTES" If a file exists in the \s-1CVS\s0 project but not locally, it is assumed that the file has been deleted, not that it may have been recently added by another user. The \&\-\-confirm option can be used to deal with this situation. .SH "AUTHOR" .IX Header "AUTHOR" Jeff Pace .SH "COPYRIGHT" .IX Header "COPYRIGHT" Copyright (c) 2002, Jeff Pace. .PP All Rights Reserved. This module is free software. It may be used, redistributed and/or modified under the terms of the Lesser \s-1GNU\s0 Public License. See http://www.gnu.org/licenses/lgpl.html for more information. cvsdelta-1.7.0.orig/cvsdelta.pod0000644000175000017500000001346510044322262016466 0ustar ericeric00000000000000=head1 NAME cvsdelta - Manage and summarize the differences between a CVS project and local files. =head1 SYNOPSIS B [options] [file ...] =head1 DESCRIPTION Manages and summarizes the differences between a CVS project and local files, showing the changes of the files themselves and of their contents. Added, changed, and deleted files are denoted with `+', `*', and `-' respectively. The number of lines are displayed. =head1 OPTIONS =over 4 =item B<-a>, B<--adds> Display the files that were added. This is done by default. =item B<-A>, B<--no-adds> Do not display the files that were added. The default is to show the added files. =item B<--banner>, B<--no-banner> Whether to show the banner, i.e., the header and footer of the output table. By default, the banner is displayed. =item B<-c>, B<--changes> Display the files that were changed. This is done by default. =item B<-C>, B<--no-changes>, B<--nodiff> Do not compare files that exist locally and in CVS. This executes significantly faster, and is useful for when one only wants to add and remove files, without getting the summary of the changes. It is also helpful for dealing with situations where CVS seems to hang after producing diff output, although changing the compression level can help with this. The default is to show the changed files. =item B<-d>, B<--deletes> Display the files that were deleted. This is done by default. =item B<-D>, B<--no-deletes> Do not display the files that were deleted. The default is to show the files that were deleted. =item B<-e>, B<--execute> Execute the associated CVS commands ("add" and "remove") for the added and deleted files. =item B<-f DATE>, B<--from-date DATE> Compare the files to their version as of the given date, rather than their current version in CVS. The date is expected to be in CVS format, for example, 2002/12/31. =item B<-F REVISION>, B<--from-revision REVISION> Compare the files to their version as of the given revision, rather than their current version in CVS. =item B<-h>, B<--help> Display a help message. =item B<-i>, B<--confirm> Interactively confirm deleted files with the user before removing them from CVS. The default is simply to execute the remove command for relevant files. This option is valid only with the --execute option. =item B<--progress>, B<--no-progress> Whether to show the progress meter as the program is working with CVS. By default, this is not shown. =item B<-q>, B<--quiet> Run with minimum output. In quiet mode, the progress meter is not displayed. =item B<-s>, B<--skip-unknown-directories> Skip directories that are not in CVS. The default behavior is to process all subdirectories. =item B<-t DATE>, B<--to-date DATE> Compare the files to their version as of the given date, rather than to the local files. The date is expected to be in CVS format, for example, 2001/08/21. This option is valid only with the --from-date or --from-revision options. =item B<-T REVISION>, B<--to-revision REVISION> Compare the files to their version as of the given revision, rather than to the local files. This is valid only with the --from-date or --from-revision options. =item B<-v>, B<--version> Display the version and exit. =item B<-V>, B<--verbose> Run with maximum output, which is written to standard error. =item B<-z [LEVEL]>, -B<--compression [LEVEL]> Set the compression to the given level for net traffic. This can be useful if the CVS server seems to hang. The default level is 3; 0 and 9 work well. =back =head1 ARGUMENTS Arguments may be either files or directories. =over 4 =item B If a file is provided, it is compared against the equivalent in CVS, if any. The .cvsignore files are applied, so that although the file was explicitly provided, it may be ignored. =item B Directories are processed recursively for files that are not filtered out by .cvsignore files. =back =head1 EXAMPLES % cvsdelta Lists the changed files. % cvsdelta --execute Lists the changed files, and executes the associated add and remove commands. % cvsdelta --compression 9 --execute --confirm --no-changes Run with the maximum compression level. Add and remove the appropriate files, but get confirmation from the user before removing any files. Do not compare files that exist both locally and in CVS. =head1 ENVIRONMENT =over 4 =item $HOME/.cvsdeltarc, .../project/.cvsdeltarc Resource files containing name/value pairs, separated by either ':' or '='. The valid fields of a .cvsdeltarc file are as follows, with their default values: quiet: false verbose: false execute: false confirm: false diff: true compression: 3 adds: true changes: true deletes: true skip-unknown-directories: false banner: true "yes" and "on" are synonymnous with "true". "no" and "off" signify "false". The values in the project .cvsdeltarc file will override those in the user's .cvsdeltarc file. The project .cvsdeltarc is assumed to be at the topmost directory in the local file hierarchy. =item CVSDELTAOPTS A string of whitespace-delimited options, the same as listed above. These values override those of the resource files. =back =head1 NOTES If a file exists in the CVS project but not locally, it is assumed that the file has been deleted, not that it may have been recently added by another user. The --confirm option can be used to deal with this situation. =head1 AUTHOR Jeff Pace =head1 COPYRIGHT Copyright (c) 2002, Jeff Pace. All Rights Reserved. This module is free software. It may be used, redistributed and/or modified under the terms of the Lesser GNU Public License. See http://www.gnu.org/licenses/lgpl.html for more information. =cut cvsdelta-1.7.0.orig/cvsdelta.spec0000644000175000017500000000263510044322262016633 0ustar ericeric00000000000000Summary: Summary and manager of CVS changes Name: cvsdelta Version: 1.7.0 Release: 1 Epoch: 0 License: LGPL Group: Development/Tools URL: http://cvsdelta.sourceforge.net/ Source: http://prdownloads.sourceforge.net/cvsdelta/cvsdelta-%{version}.tar.gz Requires: ruby BuildRoot: %{_tmppath}/%{name}-%{version}-root BuildArch: noarch Packager: Jeff Pace (jpace@incava.org) Vendor: incava.org %description Summarizes the differences between a CVS project and local files, listing the files and the number of lines that have been added, changed, and removed. Can optionally execute the related CVS commands to add and delete files. %prep %setup -q %build %install [ "$RPM_BUILD_ROOT" -a "$RPM_BUILD_ROOT" != / ] && rm -rf "$RPM_BUILD_ROOT" mkdir -p "$RPM_BUILD_ROOT"%{_bindir} mkdir -p "$RPM_BUILD_ROOT"%{_mandir}/man1/ cp cvsdelta "$RPM_BUILD_ROOT"%{_bindir} cp cvsdelta.1 "$RPM_BUILD_ROOT"%{_mandir}/man1/ %clean [ "$RPM_BUILD_ROOT" -a "$RPM_BUILD_ROOT" != / ] && rm -rf "$RPM_BUILD_ROOT" %files %defattr(-,root,root,-) %{_bindir}/cvsdelta %{_mandir}/man1/cvsdelta.1* %changelog * Tue Apr 06 2004 Jeff Pace (jpace@incava.org) 1.7.0-1 - Fixed bug reporting not-yet-commited added and removed files - Improved .spec file * Wed Apr 23 2003 Andre Costa - implemented correct use of %{buildroot} macro during 'make install' - remove temp build dir on %clean - use of predefined macros whenever possible