Class: Ace::GitCommit::Molecules::PathResolver

Inherits:
Object
  • Object
show all
Defined in:
lib/ace/git_commit/molecules/path_resolver.rb

Overview

PathResolver handles path resolution and filtering for commits

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(git_executor) ⇒ PathResolver

Returns a new instance of PathResolver.



18
19
20
21
# File 'lib/ace/git_commit/molecules/path_resolver.rb', line 18

def initialize(git_executor)
  @git = git_executor
  @last_error = nil
end

Instance Attribute Details

#last_errorObject (readonly)

Returns the value of attribute last_error.



10
11
12
# File 'lib/ace/git_commit/molecules/path_resolver.rb', line 10

def last_error
  @last_error
end

Instance Method Details

#all_paths_exist?(paths) ⇒ Boolean

Check if all paths exist or have git changes (deleted/renamed files)

Parameters:

  • paths (Array<String>)

    Paths to check

Returns:

  • (Boolean)

    True if all paths are valid



130
131
132
133
134
# File 'lib/ace/git_commit/molecules/path_resolver.rb', line 130

def all_paths_exist?(paths)
  return true if paths.nil? || paths.empty?
  result = validate_paths(paths)
  result[:invalid].empty?
end

#files_in_path(path) ⇒ Array<String>

Get all tracked files within specified path

Parameters:

  • path (String)

    Path to search

Returns:

  • (Array<String>)

    List of tracked files



67
68
69
70
71
72
73
# File 'lib/ace/git_commit/molecules/path_resolver.rb', line 67

def files_in_path(path)
  result = @git.execute("ls-files", path)
  result.strip.split("\n").reject(&:empty?)
rescue GitError => e
  @last_error = e.message
  []
end

#filter_by_paths(all_files, allowed_paths) ⇒ Array<String>

Filter files to only those within specified paths

Parameters:

  • all_files (Array<String>)

    All files

  • allowed_paths (Array<String>)

    Allowed paths

Returns:

  • (Array<String>)

    Filtered files



56
57
58
59
60
61
62
# File 'lib/ace/git_commit/molecules/path_resolver.rb', line 56

def filter_by_paths(all_files, allowed_paths)
  return all_files if allowed_paths.nil? || allowed_paths.empty?

  all_files.select do |file|
    allowed_paths.any? { |path| file_in_path?(file, path) }
  end
end

#glob_pattern?(path) ⇒ Boolean

Check if path contains glob pattern characters

Parameters:

  • path (String)

    Path to check

Returns:

  • (Boolean)

    True if path is a glob pattern



139
140
141
142
# File 'lib/ace/git_commit/molecules/path_resolver.rb', line 139

def glob_pattern?(path)
  # Check for common glob metacharacters
  path.include?("*") || path.include?("?") || path.include?("[") || path.include?("{")
end

#modified_files_in_paths(paths, staged: false) ⇒ Array<String>

Get modified files within specified paths

Parameters:

  • paths (Array<String>)

    Paths to check

  • staged (Boolean) (defaults to: false)

    Check staged or unstaged changes

Returns:

  • (Array<String>)

    Modified files



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/ace/git_commit/molecules/path_resolver.rb', line 79

def modified_files_in_paths(paths, staged: false)
  return [] if paths.nil? || paths.empty?

  args = ["diff", "--name-only"]
  args << "--cached" if staged

  modified = []
  paths.each do |path|
    result = @git.execute(*args, path)
    files = result.strip.split("\n").reject(&:empty?)
    modified.concat(files)
  end

  modified.uniq.sort
rescue GitError => e
  @last_error = e.message
  []
end

#resolve_paths(paths) ⇒ Array<String>

Resolve paths to actual file lists Expands directories, glob patterns to files within them

Parameters:

  • paths (Array<String>)

    Paths (files, directories, or glob patterns)

Returns:

  • (Array<String>)

    List of files



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/ace/git_commit/molecules/path_resolver.rb', line 27

def resolve_paths(paths)
  return [] if paths.nil? || paths.empty?

  resolved = []
  paths.each do |path|
    if glob_pattern?(path)
      # Expand glob pattern to matching tracked files
      files = expand_glob_pattern(path)
      resolved.concat(files)
    elsif File.directory?(path)
      # Get all tracked files in directory
      files = files_in_path(path)
      resolved.concat(files)
    elsif File.exist?(path)
      # Single file
      resolved << path
    else
      # Path doesn't exist - we'll validate later
      resolved << path
    end
  end

  resolved.uniq.sort
end

#simple_glob_pattern?(pattern) ⇒ Boolean

Check if pattern is a simple (non-recursive) glob pattern Simple globs like *.rb only match at current directory level

Parameters:

  • pattern (String)

    Pattern to check

Returns:

  • (Boolean)

    True if pattern is a simple glob (not recursive)



148
149
150
# File 'lib/ace/git_commit/molecules/path_resolver.rb', line 148

def simple_glob_pattern?(pattern)
  glob_pattern?(pattern) && !pattern.include?("**")
end

#suggest_recursive_pattern(pattern) ⇒ String?

Suggest a recursive alternative for simple glob patterns

Parameters:

  • pattern (String)

    Pattern to analyze

Returns:

  • (String, nil)

    Suggested recursive pattern, or nil if not applicable



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/ace/git_commit/molecules/path_resolver.rb', line 155

def suggest_recursive_pattern(pattern)
  return nil unless simple_glob_pattern?(pattern)

  # For patterns starting with *, prepend **/ for recursive matching
  # e.g., "*.rb" -> "**/*.rb"
  return "**/#{pattern}" if pattern.start_with?("*")

  # For patterns with subdirectory like "dir/*.rb", insert **/ before the glob part
  # e.g., "dir/*.rb" -> "dir/**/*.rb"
  if pattern.include?("/")
    # Find the last directory separator before the glob portion
    last_slash = pattern.rindex("/")
    dir_part = pattern[0..last_slash]
    glob_part = pattern[(last_slash + 1)..]
    return "#{dir_part}**/#{glob_part}" if glob_part.include?("*")
  end

  nil
end

#validate_paths(paths) ⇒ Hash

Validate that paths exist or have git changes (deleted/renamed files)

Parameters:

  • paths (Array<String>)

    Paths to validate

Returns:

  • (Hash)

    Validation result with :valid and :invalid paths



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/ace/git_commit/molecules/path_resolver.rb', line 101

def validate_paths(paths)
  return {valid: [], invalid: []} if paths.nil? || paths.empty?

  valid = []
  invalid = []
  git_changed_paths = nil # Lazy load
  git_changed_set = nil   # Pre-computed Set for O(1) lookups

  paths.each do |path|
    if File.exist?(path)
      valid << path
    else
      # Check if path has git changes (deleted, renamed)
      git_changed_paths ||= paths_with_git_changes
      git_changed_set ||= git_changed_paths.map { |p| p.chomp("/") }.to_set
      if path_has_git_changes?(path, git_changed_paths, git_changed_set)
        valid << path
      else
        invalid << path
      end
    end
  end

  {valid: valid, invalid: invalid}
end

#within_repository?(path) ⇒ Boolean

Check if path is within repository boundaries

Parameters:

  • path (String)

    Path to check

Returns:

  • (Boolean)

    True if path is within repository



178
179
180
181
182
183
184
185
186
187
# File 'lib/ace/git_commit/molecules/path_resolver.rb', line 178

def within_repository?(path)
  return false unless File.exist?(path)

  expanded = File.expand_path(path)
  repo_root = @git.execute("rev-parse", "--show-toplevel").strip
  expanded.start_with?(File.expand_path(repo_root))
rescue GitError => e
  @last_error = e.message
  false
end