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
-
DEFAULT_REVISION =
# File 'lib/rubocop/changed_files.rb', line 13'HEAD' -
FILESYSTEM_ENCODING =
# File 'lib/rubocop/changed_files.rb', line 14Encoding.find('filesystem')
Class Method Summary
- .new(revision = nil) ⇒ ChangedFiles constructor Internal use only
-
.revision?(name) ⇒ Boolean
Internal use only
Whether git can resolve
nameto a commit in the current repository.
Instance Attribute Summary
- #unborn_head? ⇒ Boolean readonly private Internal use only
Instance Method Summary
- #filter(files) ⇒ Array<String> Internal use only
-
#paths ⇒ Set<String>
Internal use only
Files that were added or modified since the revision, as absolute paths.
-
#empty_tree
private
Internal use only
Hashed rather than hardcoded, since its id depends on the repository’s object format (SHA-1 or SHA-256).
-
#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(revision = @revision) private Internal use only
- #repository_root private Internal use only
-
#split_paths(output)
private
Internal use only
The
-zabove 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
# File 'lib/rubocop/changed_files.rb', line 25
def initialize(revision = nil) @revision = revision || DEFAULT_REVISION end
Class Method Details
.revision?(name) ⇒ Boolean
Whether git can resolve name to a commit in the current repository.
# File 'lib/rubocop/changed_files.rb', line 17
def self.revision?(name) _stdout, _stderr, status = Open3.capture3('git', 'rev-parse', '--verify', '--quiet', "#{name}^{commit}") status.success? rescue Errno::ENOENT false end
Instance Attribute Details
#unborn_head? ⇒ Boolean (readonly, private)
# File 'lib/rubocop/changed_files.rb', line 69
def unborn_head? !self.class.revision?(DEFAULT_REVISION) end
Instance Method Details
#empty_tree (private)
Hashed rather than hardcoded, since its id depends on the repository’s object format (SHA-1 or SHA-256).
# File 'lib/rubocop/changed_files.rb', line 75
def empty_tree git('hash-object', '-t', 'tree', '--stdin').chomp end
#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.
# File 'lib/rubocop/changed_files.rb', line 105
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
#filter(files) ⇒ Array<String>
# File 'lib/rubocop/changed_files.rb', line 42
def filter(files) changed = paths files.select { |file| changed.include?(file) } end
#git(*args) (private)
[ GitHub ]#git_error_message(stderr) (private)
[ GitHub ]# File 'lib/rubocop/changed_files.rb', line 127
def (stderr) = stderr.strip = 'git failed with no output on standard error.' if .empty? "--changed could not ask git which files changed: #{}" end
#modified_paths(revision = @revision) (private)
[ GitHub ]# File 'lib/rubocop/changed_files.rb', line 53
def modified_paths(revision = @revision) # `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 revision == DEFAULT_REVISION && unborn_head? # A repository without commits has no `HEAD` to diff against, but files # can already be staged in it. Against the empty tree every one of them # counts as changed, which is what the caller wants to hear rather than # an error about `HEAD` not resolving. modified_paths(empty_tree) end
#paths ⇒ Set<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.
# File 'lib/rubocop/changed_files.rb', line 35
def paths root = repository_root (modified_paths + untracked_paths).to_set { |path| File.(path, root) } end
#repository_root (private)
[ GitHub ]# File 'lib/rubocop/changed_files.rb', line 49
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.
# File 'lib/rubocop/changed_files.rb', line 91
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 79
def untracked_paths # Unlike `git diff`, `ls-files` only looks under the current directory, so # the `:/` pathspec widens it to the whole repository, and `--full-name` # anchors the paths to the repository root the way `git diff` does. split_paths( git('ls-files', '--others', '--exclude-standard', '--full-name', '-z', '--', ':/') ) end