Class: Refinement::Changeset
- Inherits:
-
Object
- Object
- Refinement::Changeset
- Defined in:
- lib/refinement/changeset.rb,
lib/refinement/changeset/file_modification.rb
Overview
Represents a set of changes in a repository between a prior revision and the current state
Defined Under Namespace
Classes: FileModification, GitError
Instance Attribute Summary collapse
-
#repository ⇒ Pathname
readonly
The path to the repository.
Class Method Summary collapse
-
.from_git(repository:, base_revision:) ⇒ Changeset
The changes in the given git repository between the given revision and HEAD.
-
.parse_raw_diff(diff, repository:, base_revision:) ⇒ Array<FileModification>
Parses the raw diff into FileModification objects.
Instance Method Summary collapse
-
#find_modification_for_glob(absolute_glob:) ⇒ FileModification, Nil
The modification for the given absolute glob, or ‘nil` if no files matching the glob were modified.
-
#find_modification_for_path(absolute_path:) ⇒ FileModification, Nil
The changeset for the given absolute path, or ‘nil` if the given path is un-modified.
-
#find_modification_for_yaml_keypath(absolute_path:, keypath:) ⇒ FileModification, Nil
A modification and yaml diff for the keypath at the given absolute path, or ‘nil` if the value at the given keypath is un-modified.
-
#initialize(repository:, modifications:) ⇒ Changeset
constructor
A new instance of Changeset.
Constructor Details
#initialize(repository:, modifications:) ⇒ Changeset
Returns a new instance of Changeset.
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/refinement/changeset.rb', line 24 def initialize(repository:, modifications:) @repository = repository @modifications = self.class.add_directories(modifications).uniq.freeze @modified_paths = {} @modifications .each { |mod| @modified_paths[mod.path] = mod } .each { |mod| @modified_paths[mod.prior_path] ||= mod if mod.prior_path } @modified_paths.freeze @modified_absolute_paths = {} @modified_paths .each { |path, mod| @modified_absolute_paths[path.(repository).freeze] = mod } @modified_absolute_paths.freeze end |
Instance Attribute Details
#repository ⇒ Pathname (readonly)
Returns the path to the repository.
14 15 16 |
# File 'lib/refinement/changeset.rb', line 14 def repository @repository end |
Class Method Details
.from_git(repository:, base_revision:) ⇒ Changeset
Returns the changes in the given git repository between the given revision and HEAD.
140 141 142 143 144 145 146 147 148 149 |
# File 'lib/refinement/changeset.rb', line 140 def self.from_git(repository:, base_revision:) raise ArgumentError, "must be given a Pathname for repository, got #{repository.inspect}" unless repository.is_a?(Pathname) raise ArgumentError, "must be given a String for base_revision, got #{base_revision.inspect}" unless base_revision.is_a?(String) merge_base = git!('merge-base', base_revision, 'HEAD', chdir: repository).strip diff = git!('diff', '--raw', '-z', merge_base, chdir: repository) modifications = parse_raw_diff(diff, repository: repository, base_revision: merge_base).freeze new(repository: repository, modifications: modifications) end |
.parse_raw_diff(diff, repository:, base_revision:) ⇒ Array<FileModification>
Parses the raw diff into FileModification objects
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/refinement/changeset.rb', line 171 def self.parse_raw_diff(diff, repository:, base_revision:) # since we're null separating the chunks (to avoid dealing with path escaping) we have to reconstruct # the chunks into individual diff entries. entries always start with a colon so we can use that to signal if # we're on a new entry parsed_lines = diff.split("\0").each_with_object([]) do |chunk, lines| lines << [] if chunk.start_with?(':') lines.last << chunk end parsed_lines.map do |split_line| # change chunk (letter + optional similarity percentage) will always be the last part of first line chunk change_chunk = split_line[0].split(/\s/).last new_path = Pathname(split_line[2]).freeze if split_line[2] old_path = Pathname(split_line[1]).freeze prior_path = old_path if new_path # new path if one exists, else existing path. new path only exists for rename and copy changed_path = new_path || old_path change_character = change_chunk[0] # returns 0 when a similarity percentage isn't specified by git. _similarity = change_chunk[1..3].to_i FileModification.new( path: changed_path, type: CHANGE_CHARACTERS[change_character], prior_path: prior_path, contents_reader: -> { repository.join(changed_path).read }, prior_contents_reader: lambda { git!('show', "#{base_revision}:#{prior_path || changed_path}", chdir: repository) } ) end end |
Instance Method Details
#find_modification_for_glob(absolute_glob:) ⇒ FileModification, Nil
Will only return a single (arbitrary) matching modification, even if there are multiple modifications that match the glob
Returns the modification for the given absolute glob, or ‘nil` if no files matching the glob were modified.
114 115 116 117 118 119 120 121 122 |
# File 'lib/refinement/changeset.rb', line 114 def find_modification_for_glob(absolute_glob:) absolute_globs = dir_glob_equivalent_patterns(absolute_glob) _path, modification = modified_absolute_paths.find do |absolute_path, _modification| absolute_globs.any? do |glob| File.fnmatch?(glob, absolute_path, File::FNM_CASEFOLD | File::FNM_PATHNAME) end end modification end |
#find_modification_for_path(absolute_path:) ⇒ FileModification, Nil
Returns the changeset for the given absolute path, or ‘nil` if the given path is un-modified.
62 63 64 |
# File 'lib/refinement/changeset.rb', line 62 def find_modification_for_path(absolute_path:) modified_absolute_paths[absolute_path] end |
#find_modification_for_yaml_keypath(absolute_path:, keypath:) ⇒ FileModification, Nil
Returns a modification and yaml diff for the keypath at the given absolute path, or ‘nil` if the value at the given keypath is un-modified.
128 129 130 131 132 133 134 135 |
# File 'lib/refinement/changeset.rb', line 128 def find_modification_for_yaml_keypath(absolute_path:, keypath:) return unless (file_modification = find_modification_for_path(absolute_path: absolute_path)) diff = file_modification.yaml_diff(keypath) return unless diff [file_modification, diff] end |