123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::ChangedFiles Private

Relationships & Source Files
Inherits: Object
Defined in: lib/rubocop/changed_files.rb

Overview

Lists the files that differ from a git revision, for --changed.

Shelling out to #git keeps RuboCop free of a version control dependency, and means the answer is exactly what git would give on the command line.

Constant Summary

Class Method Summary

Instance Attribute Summary

Instance Method Summary

  • #paths ⇒ Set<String> Internal use only

    Files that were added or modified since the revision, as absolute paths.

  • #filesystem_path(path) private Internal use only

    git hands back bytes, which Ruby tags with the encoding the process happens to have been started in - splitting those bytes as US-ASCII raises on any path that is not.

  • #git(*args) private Internal use only
  • #git_error_message(stderr) private Internal use only
  • #modified_paths private Internal use only
  • #repository_root private Internal use only
  • #split_paths(output) private Internal use only

    The -z above is what makes these usable: without it git wraps any path that is not plain ASCII in quotes and octal escapes, and those files drop out of the comparison without a word.

  • #untracked_paths private Internal use only

Constructor Details

.new(revision = nil) ⇒ ChangedFiles

[ GitHub ]

  
# File 'lib/rubocop/changed_files.rb', line 16

def initialize(revision = nil)
  @revision = revision || DEFAULT_REVISION
end

Instance Attribute Details

#unborn_head?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/rubocop/changed_files.rb', line 53

def unborn_head?
  return false unless @revision == DEFAULT_REVISION

  _stdout, _stderr, status = Open3.capture3('git', 'rev-parse', '--verify', '--quiet', 'HEAD')
  !status.success?
end

Instance Method Details

#filesystem_path(path) (private)

git hands back bytes, which Ruby tags with the encoding the process happens to have been started in - splitting those bytes as US-ASCII raises on any path that is not. They have to end up in the encoding Dir.glob uses, or a path with a non-ASCII character in it never matches the file list it is compared against.

On POSIX those bytes are already the filesystem’s own, so the encoding is simply corrected. Windows is the exception: git converts paths to UTF-8 on the way out, while Ruby reports file names in the local code page, so the two have to be reconciled.

[ GitHub ]

  
# File 'lib/rubocop/changed_files.rb', line 83

def filesystem_path(path)
  path = path.dup

  return path.force_encoding(FILESYSTEM_ENCODING) unless Platform.windows?

  path.force_encoding(Encoding::UTF_8).encode(FILESYSTEM_ENCODING)
rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError
  # A name the local code page cannot represent. Nothing good is left to do
  # with it, and dropping it would be worse than reporting it as-is.
  path.force_encoding(FILESYSTEM_ENCODING)
end

#git(*args) (private)

[ GitHub ]

  
# File 'lib/rubocop/changed_files.rb', line 95

def git(*args)
  stdout, stderr, status = Open3.capture3('git', *args)

  raise Error, git_error_message(stderr) unless status.success?

  stdout
rescue Errno::ENOENT
  raise Error, '--changed requires git to be installed and on your PATH.'
end

#git_error_message(stderr) (private)

[ GitHub ]

  
# File 'lib/rubocop/changed_files.rb', line 105

def git_error_message(stderr)
  message = stderr.strip
  message = 'git failed with no output on standard error.' if message.empty?

  "--changed could not ask git which files changed: #{message}"
end

#modified_paths (private)

[ GitHub ]

  
# File 'lib/rubocop/changed_files.rb', line 38

def modified_paths
  # `diff.relative` would make git report paths relative to the current
  # directory rather than to the repository root.
  split_paths(
    git('-c', 'diff.relative=false', 'diff', '--name-only', '--diff-filter=d', '-z', @revision)
  )
rescue Error
  raise unless unborn_head?

  # A repository without commits has nothing to diff against, and
  # everything in it is untracked - which is what the caller wants to hear
  # rather than an error about `HEAD` not resolving.
  []
end

#pathsSet<String>

Files that were added or modified since the revision, as absolute paths. Deleted files are left out - there is nothing left to inspect - and untracked files are included, since a file that is new to the working tree has changed as surely as one git already knows about.

[ GitHub ]

  
# File 'lib/rubocop/changed_files.rb', line 26

def paths
  root = repository_root

  (modified_paths + untracked_paths).to_set { |path| File.expand_path(path, root) }
end

#repository_root (private)

[ GitHub ]

  
# File 'lib/rubocop/changed_files.rb', line 34

def repository_root
  filesystem_path(git('rev-parse', '--show-toplevel').b.chomp)
end

#split_paths(output) (private)

The -z above is what makes these usable: without it git wraps any path that is not plain ASCII in quotes and octal escapes, and those files drop out of the comparison without a word.

[ GitHub ]

  
# File 'lib/rubocop/changed_files.rb', line 69

def split_paths(output)
  output.b.split("\0").reject(&:empty?).map { |path| filesystem_path(path) }
end

#untracked_paths (private)

[ GitHub ]

  
# File 'lib/rubocop/changed_files.rb', line 60

def untracked_paths
  # `--full-name` anchors the paths to the repository root, the way the
  # paths from `git diff` already are.
  split_paths(git('ls-files', '--others', '--exclude-standard', '--full-name', '-z'))
end