#!/usr/bin/ruby # # Calculates changes between consecutive versions of source code. Createsa a # file named 'changes' in each versions directory that contains information # about added, deleted and modified files. # # Software Engineering Group # University of Bremen # # Feel free to adjust to your own needs. # require "md5" require "find" # Determines if the given path is relevant. Returns true if the path ends with a # file extension related to the given programming language and false otherwise. def is_relevant(p, language) if language == "cpp" then return p[p.length - 4 .. p.length - 1].downcase == ".cpp" || p[p.length - 3 .. p.length - 1].downcase == ".cc" || p[p.length - 2 .. p.length - 1].downcase == ".c" p[p.length - 2 .. p.length - 1].downcase == ".h" elsif language == "c" then return p[p.length - 2 .. p.length - 1].downcase == ".c" || p[p.length - 2 .. p.length - 1].downcase == ".h" elsif language == "java" then return p[p.length - 5 .. p.length - 1].downcase == ".java" elsif language == "ada" then return p[p.length - 4 .. p.length - 1].downcase == ".adb" || p[p.length - 4 .. p.length - 1].downcase == ".ads" elsif language == "cobol" then return p[p.length - 4 .. p.length - 1].downcase == ".cbl" || p[p.length - 4 .. p.length - 1].downcase == ".cpy" end return false; end # Extract all relevant files found in the given directory. def get_files(d, language) print "Getting files for #{d}... " $stdout.flush files = Hash.new Find.find(d) do |f| if FileTest.file?(f) && is_relevant(f, language) then files[f[d.length + 1 .. f.length - 1]] = false end end puts "#{files.length} files" return files end # ------------------------------------------------------------------------- hash # # Returns an MD5 hash for the contents of the given file. If the file does not # exist or is not readable, 0 is returned. # def hash(file) return 0 unless FileTest.readable?(file) return MD5.new(File.read(file)).hexdigest end # ------------------------------------------------------------------------- MAIN unless ARGV.length >= 2 && ARGV.length <= 3 && ["ada", "c", "cpp", "java", "cobol"].member?(ARGV[0]) puts "Usage: ichanges [-single]" puts " language - determines relevant files, one of [ada, c, cpp, java, cobol]" puts " directory - directory containing one subdirectory for each version" puts " -single - create changes for a single version only (optional)" exit end language = ARGV[0] main_dir = ARGV[1] main_dir = main_dir[main_dir.length - 1 .. main_dir.length - 1] == "/" ? main_dir : main_dir + "/" if ARGV.length == 3 then puts "Creating changes for a single version only..." outfile = File.open(main_dir + "changes", "w") get_files(main_dir[0 .. main_dir.length - 2], language).each do |f,bla| outfile.puts "A \"#{f}\"" end outfile.close puts "Finished" exit end # Read directories for versions print "Reading version directories... " $stdout.flush versions = Dir.entries(main_dir) versions.delete_if do |e| !FileTest.directory?(main_dir + e) or e[0 .. 0] == "." end versions.sort! puts "#{versions.length} found" versionFiles = Hash.new versions.each do |v| versionFiles[v] = get_files(main_dir + v, language) end if versions.length > 0 then outfile = File.open(main_dir + versions[0] + "/changes", "w") versionFiles[versions[0]].each do |f,bla| outfile.puts "A \"#{f}\"" end outfile.close end # Main loop. Create changes for all versions but the first for i in 1 .. versions.length - 1 old_version = versions[i - 1] new_version = versions[i] puts "\rCreating changes from version #{old_version} to #{new_version} [#{i}/#{versions.length - 1}]... " $stdout.flush outfile = File.open(main_dir + new_version + "/changes", "w") adds = Hash.new dels = Hash.new keys_done = 0 # Iterate over all relevant files in OLD version print "Detecting modified and deleted files... 0%" $stdout.flush versionFiles[old_version].each_key do |f| # If the file exists in new version test it for changes. If it is # modified write the changes to the file. if versionFiles[new_version][f] != nil then versionFiles[new_version][f] = true result = `diff -E -b \"#{main_dir + old_version + "/" + f}\" \"#{main_dir + new_version + "/" + f}\"` if hash(main_dir + old_version + "/" + f) != hash(main_dir + new_version + "/" + f) then #result.strip != "" then outfile.print "M \"" + f + "\"" result.each_line do |line| if line[0 .. 0] >= "0" and line[0 .. 0] <= "9" then outfile.print " " + line.strip end end outfile.puts "" end else # Otherwise the file has been deleted dels[f] = false end # Inform about process keys_done = keys_done + 1 if keys_done % 50 == 0 then percent = (keys_done.to_f / versionFiles[old_version].size.to_f * 100.0).to_i print "\rDetecting modified and deleted files... #{percent}%" $stdout.flush end end # Iterate over all relevant files in NEW version to find added files print "\rDetecting added files... " $stdout.flush versionFiles[new_version].each_pair do |f,exists| if !exists then adds[f] = false end end # Write remaining additions and deletions to the change file print "\rWrite changes... " $stdout.flush adds.each do |a,relocated| outfile.puts "A \"#{a}\"" unless relocated end dels.each do |d,relocated| outfile.puts "D \"#{d}\"" unless relocated end # Close the change file for this version and continue with the next outfile.close end puts "\rFinished "