Module: Ace::Git::Atoms::CommandExecutor
- Defined in:
- lib/ace/git/atoms/command_executor.rb
Overview
Pure functions for executing git commands safely Migrated from ace-git-diff
Lock Retry Behavior:
-
Automatically retries git commands that encounter .git/index.lock errors
-
Uses progressive delays: 1s, 2s, 3s, 4s (total 10s across 4 retries)
-
On each retry, attempts to clean orphaned locks (dead PID) or stale locks (>10s)
-
Configurable via lock_retry section in .ace/git/config.yml
-
Only git commands are retried; non-git commands fail immediately
This retry logic prevents “Unable to create .git/index.lock” errors that commonly occur in multi-worktree environments or when operations are interrupted (Ctrl+C, crashes, timeouts).
Class Method Summary collapse
-
.changed_files(range = nil) ⇒ Array<String>
Get list of changed files.
-
.current_branch ⇒ String?
Get current branch name or commit SHA if detached.
-
.execute(*command_parts, timeout: Ace::Git.git_timeout, env: nil) ⇒ Hash
Execute a command safely using array arguments to prevent command injection.
-
.git_diff(*args, raise_on_error: false) ⇒ String
Execute git diff command.
-
.has_staged_changes? ⇒ Boolean
Check if there are staged changes.
-
.has_unstaged_changes? ⇒ Boolean
Check if there are unstaged changes.
-
.has_untracked_changes? ⇒ Boolean
Check if there are untracked changes.
-
.in_git_repo? ⇒ Boolean
Check if we’re in a git repository.
-
.ref_exists?(ref) ⇒ Boolean
Check whether a git reference exists in the repository.
-
.repo_root ⇒ String?
Get repository root path.
-
.staged_diff ⇒ String
Get staged changes.
-
.tracking_branch ⇒ String?
Get remote tracking branch.
-
.working_diff ⇒ String
Get working directory changes.
Class Method Details
.changed_files(range = nil) ⇒ Array<String>
Get list of changed files
209 210 211 212 213 214 215 216 217 218 |
# File 'lib/ace/git/atoms/command_executor.rb', line 209 def changed_files(range = nil) range = "origin/main...HEAD" if range.nil? && ref_exists?("origin/main") args = ["git", "diff", "--name-only"] args << range if range && !range.empty? result = execute(*args) return [] unless result[:success] result[:output].split("\n").map(&:strip).reject(&:empty?) end |
.current_branch ⇒ String?
Get current branch name or commit SHA if detached
180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/ace/git/atoms/command_executor.rb', line 180 def current_branch result = execute("git", "rev-parse", "--abbrev-ref", "HEAD") return nil unless result[:success] branch = result[:output].strip return branch unless branch == "HEAD" # Detached HEAD - return commit SHA instead sha_result = execute("git", "rev-parse", "HEAD") sha_result[:success] ? sha_result[:output].strip : nil end |
.execute(*command_parts, timeout: Ace::Git.git_timeout, env: nil) ⇒ Hash
Execute a command safely using array arguments to prevent command injection
29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/ace/git/atoms/command_executor.rb', line 29 def execute(*command_parts, timeout: Ace::Git.git_timeout, env: nil) # Check if lock retry is enabled (default: true) lock_retry_config = Ace::Git.config["lock_retry"] lock_retry_enabled = lock_retry_config.nil? || lock_retry_config["enabled"] != false if lock_retry_enabled && !command_parts.empty? && command_parts.first == "git" execute_with_lock_retry(command_parts, timeout: timeout, env: env, config: lock_retry_config) else execute_once(command_parts, timeout: timeout, env: env) end end |
.git_diff(*args, raise_on_error: false) ⇒ String
Execute git diff command
146 147 148 149 150 151 152 153 154 155 |
# File 'lib/ace/git/atoms/command_executor.rb', line 146 def git_diff(*args, raise_on_error: false) result = execute("git", "diff", *args) if result[:success] result[:output] elsif raise_on_error raise Ace::Git::GitError, "git diff failed: #{result[:error]}" else "" end end |
.has_staged_changes? ⇒ Boolean
Check if there are staged changes
228 229 230 |
# File 'lib/ace/git/atoms/command_executor.rb', line 228 def has_staged_changes? !staged_diff.strip.empty? end |
.has_unstaged_changes? ⇒ Boolean
Check if there are unstaged changes
222 223 224 |
# File 'lib/ace/git/atoms/command_executor.rb', line 222 def has_unstaged_changes? !working_diff.strip.empty? end |
.has_untracked_changes? ⇒ Boolean
Check if there are untracked changes
234 235 236 237 |
# File 'lib/ace/git/atoms/command_executor.rb', line 234 def has_untracked_changes? result = execute("git", "ls-files", "--others", "--exclude-standard") result[:success] && !result[:output].strip.empty? end |
.in_git_repo? ⇒ Boolean
Check if we’re in a git repository
173 174 175 176 |
# File 'lib/ace/git/atoms/command_executor.rb', line 173 def in_git_repo? result = execute("git", "rev-parse", "--git-dir") result[:success] end |
.ref_exists?(ref) ⇒ Boolean
Check whether a git reference exists in the repository.
243 244 245 246 247 248 |
# File 'lib/ace/git/atoms/command_executor.rb', line 243 def ref_exists?(ref) return false if ref.nil? || ref.strip.empty? result = execute("git", "rev-parse", "--verify", "#{ref}^{}") result[:success] end |
.repo_root ⇒ String?
Get repository root path
194 195 196 197 |
# File 'lib/ace/git/atoms/command_executor.rb', line 194 def repo_root result = execute_once(["git", "rev-parse", "--show-toplevel"], timeout: Ace::Git.git_timeout, env: nil) result[:success] ? result[:output].strip : nil end |
.staged_diff ⇒ String
Get staged changes
159 160 161 162 |
# File 'lib/ace/git/atoms/command_executor.rb', line 159 def staged_diff result = execute("git", "diff", "--cached") result[:success] ? result[:output] : "" end |
.tracking_branch ⇒ String?
Get remote tracking branch
201 202 203 204 |
# File 'lib/ace/git/atoms/command_executor.rb', line 201 def tracking_branch result = execute("git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}") result[:success] ? result[:output].strip : nil end |
.working_diff ⇒ String
Get working directory changes
166 167 168 169 |
# File 'lib/ace/git/atoms/command_executor.rb', line 166 def working_diff result = execute("git", "diff") result[:success] ? result[:output] : "" end |