################################################################## # Licensing Information # # # # The following code is licensed, as standalone code, under # # the Ruby License, unless otherwise directed within the code. # # # # For information on the license of this code when distributed # # with and used in conjunction with the other modules in the # # Amp project, please see the root-level LICENSE file. # # # # © Michael J. Edgar and Ari Brown, 2009-2010 # # # ################################################################## module Amp module Core module Repositories ## # = CommonStagingAreaMethods # # These methods are common to all staging areas, and this module is mixed into # the AbstractStagingArea class. This guarantees that all staging areas will # have these methods. # # No methods should be placed into this module unless it relies on methods in the # general API for staging areas. module CommonStagingAreaMethods ## # Returns whether or not the repository is tracking the given file. # # @api # @param [String] filename the file to look up # @return [Boolean] are we tracking the given file? def tracking?(filename) file_status(filename) != :untracked end ## # Helper method that filters out a list of explicit filenames that do not # belong in the repository. It then stores the File stats of the files # it keeps, and returns any explicitly named directories. # # @param [Array] files the files to examine # @param [Amp::Match] match the matcher object # @return [Array>] returns an array: [file_stats, directories] def examine_named_files(files, match) results, work = {vcs_dir => true}, [] # ignore the .hg files.reject {|f| results[f] || f == ""}.sort.each do |file| path = File.join(repo.root, file) if File.exist?(path) # we'll take it! but only if it's a directory, which means we have # more work to do... if File.directory?(path) # add it to the list of dirs we have to search in work << File.join(repo.root, file) unless ignoring_directory? file elsif File.file?(path) || File.symlink?(path) # ARGH WE FOUND ZE BOOTY results[file] = File.lstat path else # user you are a fuckup in life please exit the world UI::warn "#{file}: unsupported file type (type is #{File.ftype file})" results[file] = nil if tracking? file end else prefix = file + '/' unless all_files.find { |f, _| f == file || f.start_with?(prefix) } bad_type[file] results[file] = nil if (tracking?(file) || !ignoring_file?(file)) && match.call(file) end end end [results, work] end ## # Helper method that runs match's patterns on every non-ignored file in # the repository's directory. # # @param [Hash] found_files the already found files (we don't want to search them # again) # @param [Array] dirs the directories to search # @param [Amp::Match] match the matcher object that runs patterns against # filenames # @return [Hash] the updated found_files hash def find_with_patterns(found_files, dirs, match) results = found_files Find.find(*dirs) do |f| tf = f[(repo.root.size+1)..-1] Find.prune if results[tf] stats = File.lstat f match_result = match.call tf tracked = tracking? tf if File.directory? f Find.prune if ignoring_file? tf results[tf] = nil if tracked && match_result elsif File.file?(f) || File.symlink?(f) if match_result && (tracked || !ignoring_file?(tf)) results[tf] = stats end elsif tracked && match_result results[tf] = nil end end results end ## # Walk recursively through the directory tree, finding all # files matched by the regexp in match. # # Step 1: find all explicit files # Step 2: visit subdirectories # Step 3: report unseen items in the @files hash # # @todo this is still tied to hg # @param [Boolean] unknown # @param [Boolean] ignored # @return [Hash{String => [NilClass, File::Stat]}] nil for directories and # stuff, File::Stat for files and links def walk(unknown, ignored, match = Amp::Match.new { true }) if ignored @ignore_all = false elsif not unknown @ignore_all = true end files = (match.files || []).uniq # why do we overwrite the entire array if it includes the current dir? # we even kill posisbly good things files = [''] if files.include?('.') # strange thing to do # Step 1: find all explicit files results, found_directories = examine_named_files files, match work = [repo.root] + found_directories # Run the patterns results = find_with_patterns(results, work, match) # step 3: report unseen items in @files visit = all_files.select {|f| !results[f] && match.call(f) }.sort visit.each do |file| path = File.join(repo.root, file) keep = File.exist?(path) && (File.file?(path) || File.symlink(path)) results[file] = keep ? File.lstat(path) : nil end results.delete vcs_dir @ignore_all = nil # reset this results end ## # what's the current state of life, man! # Splits up all the files into modified, clean, # added, deleted, unknown, ignored, or lookup-needed. # # @param [Boolean] ignored do we collect the ignore files? # @param [Boolean] clean do we collect the clean files? # @param [Boolean] unknown do we collect the unknown files? # @param [Amp::Match] match the matcher # @return [Hash{Symbol => Array}] a hash of the filestatuses and their files def status(ignored, clean, unknown, match = Match.new { true }) list_ignored, list_clean, list_unknown = ignored, clean, unknown lookup, modified, added, unknown, ignored = [], [], [], [], [] moved, copied, removed, deleted, clean = [], [], [], [], [] delta = 0 walk(list_unknown, list_ignored, match).each do |file, st| next if file.nil? unless tracking?(file) if list_ignored && ignoring_directory?(file) ignored << file elsif list_unknown unknown << file unless ignoring_file?(file) end next # on to the next one, don't do the rest end # here's where we split up the files state = file_status file delta += calculate_delta(file, st) if !st && [:normal, :modified, :added].include?(state) # add it to the deleted folder if it should be here but isn't deleted << file elsif state == :normal case file_precise_status(file, st) when :modified modified << file when :lookup lookup << file when :clean clean << file if list_clean end elsif state == :merged modified << file elsif state == :added added << file elsif state == :removed removed << file end end # # This code creates the copied and moved arrays # # # # ugh this should be optimized # # as in, built into the code above ^^^^^ # dirstate.copy_map.each do |dst, src| # # assume that if +src+ is in +removed+ then +dst+ is in +added+ # # we know that this part will be COPIES # if removed.include? src # removed.delete src # added.delete dst # copied << [src, dst] # elsif added.include? dst # these are the MOVES # added.delete dst # moved << [src, dst] # end # end r = { :modified => modified.sort , # those that have clearly been modified :added => added.sort , # those that are marked for adding :removed => removed.sort , # those that are marked for removal :deleted => deleted.sort , # those that should be here but aren't :unknown => unknown.sort , # those that aren't being tracked :ignored => ignored.sort , # those that are being deliberately ignored :clean => clean.sort , # those that haven't changed :lookup => lookup.sort , # those that need to be content-checked to see if they've changed #:copied => copied.sort_by {|a| a[0] }, # those that have been copied #:moved => moved.sort_by {|a| a[0] }, # those that have been moved :delta => delta # how many bytes have been added or removed from files (not bytes that have been changed) } end end end end end