Module: DocOpsLab::Dev::GitBranch

Defined in:
lib/docopslab/dev/git_branch.rb

Overview

Git branch safety utilities for Rake tasks

Provides methods to safely handle branch switching by checking for:

  • Uncommitted changes (modified tracked files)

  • Untracked files that would conflict with target branch

Examples:

Basic usage

include DocOpsLab::Dev::GitBranch

current = git_current_branch
if git_safe_to_switch?('gh-pages')
  system("git checkout gh-pages")
else
  exit 1
end

With custom error handling

git_ensure_clean_switch!('deploy-branch') do |conflicts|
  puts "Found conflicts: #{conflicts.join(', ')}"
end

Instance Method Summary collapse

Instance Method Details

#git_conflicting_files(branch) ⇒ Array<String>?

Find untracked files that would conflict with target branch

An untracked file conflicts if:

  • It exists in the working directory (untracked)

  • A file with the same path exists in the target branch

  • Switching branches would require overwriting the untracked file

Parameters:

  • branch (String)

    the target branch name

Returns:

  • (Array<String>)

    list of conflicting file paths

  • (nil)

    if target branch doesn’t exist



81
82
83
84
85
86
87
# File 'lib/docopslab/dev/git_branch.rb', line 81

def git_conflicting_files branch
  branch_files = git_files_in_branch(branch)
  return nil if branch_files.nil?

  untracked = git_untracked_files
  untracked & branch_files # Intersection
end

#git_current_branchString

Get the current branch name

Returns:

  • (String)

    the current branch name

Raises:

  • (RuntimeError)

    if not in a git repository



31
32
33
34
35
36
# File 'lib/docopslab/dev/git_branch.rb', line 31

def git_current_branch
  branch = `git branch --show-current 2>&1`.strip
  raise 'Not in a git repository' if $CHILD_STATUS.exitstatus != 0

  branch
end

#git_ensure_clean_switch!(branch, verbose: true) {|conflicts| ... } ⇒ void

This method returns an undefined value.

Ensure it’s safe to switch branches, exit if not

This is a convenience method that calls git_safe_to_switch? and exits with status 1 if not safe.

Parameters:

  • branch (String)

    the target branch name

  • verbose (Boolean) (defaults to: true)

    whether to print detailed messages

Yields:

  • (conflicts)

    optional block to run if conflicts found

Yield Parameters:

  • conflicts (Array<String>)

    list of conflicting files



136
137
138
139
140
141
142
143
144
145
146
# File 'lib/docopslab/dev/git_branch.rb', line 136

def git_ensure_clean_switch! branch, verbose: true
  return if git_safe_to_switch?(branch, verbose: verbose)

  # If block given, call it with conflicts before exiting
  if block_given?
    conflicts = git_conflicting_files(branch) || []
    yield(conflicts)
  end

  exit 1
end

#git_files_in_branch(branch) ⇒ Array<String>?

Get list of files in target branch

Parameters:

  • branch (String)

    the target branch name

Returns:

  • (Array<String>)

    list of file paths in the branch

  • (nil)

    if branch doesn’t exist



62
63
64
65
66
67
68
69
# File 'lib/docopslab/dev/git_branch.rb', line 62

def git_files_in_branch branch
  # Check if branch exists
  result = `git rev-parse --verify #{branch} 2>/dev/null`.strip
  return nil if result.empty?

  # List all files in the branch
  `git ls-tree -r #{branch} --name-only`.strip.split("\n")
end

#git_has_uncommitted_changes?Boolean

Check if working directory has uncommitted changes

Uses ‘git status –porcelain` to detect:

  • Modified tracked files

  • Staged changes

  • Deleted files

Returns:

  • (Boolean)

    true if there are uncommitted changes



46
47
48
# File 'lib/docopslab/dev/git_branch.rb', line 46

def git_has_uncommitted_changes?
  !`git status --porcelain`.strip.empty?
end

#git_on_branch(branch, verbose: true) { ... } ⇒ Object

Execute a block on a different branch, then return to original

Safely switches to target branch, executes block, then returns to original branch. Ensures clean state before switching.

Examples:

git_on_branch('gh-pages') do
  # Do work on gh-pages
  FileUtils.cp_r('_site/*', '.')
end
# Automatically returns to original branch

Parameters:

  • branch (String)

    the target branch to switch to

  • verbose (Boolean) (defaults to: true)

    whether to print detailed messages

Yields:

  • the block to execute on the target branch

Returns:

  • (Object)

    the return value of the block

Raises:

  • (RuntimeError)

    if branch switch fails or block raises



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/docopslab/dev/git_branch.rb', line 165

def git_on_branch branch, verbose: true
  original_branch = git_current_branch

  # Safety check
  git_ensure_clean_switch!(branch, verbose: verbose)

  begin
    puts "📦 Switching to #{branch} branch..." if verbose
    system("git checkout #{branch}") or raise "Failed to checkout #{branch}"

    # Execute the block
    result = yield

    result
  ensure
    # Always return to original branch
    if git_current_branch != original_branch
      puts "🔄 Returning to #{original_branch} branch..." if verbose
      system("git checkout #{original_branch}")
    end
  end
end

#git_safe_to_switch?(branch, verbose: true) ⇒ Boolean

Check if it’s safe to switch to target branch

Safe to switch if:

  • No uncommitted changes in tracked files

  • No untracked files that would conflict with target branch

Parameters:

  • branch (String)

    the target branch name

  • verbose (Boolean) (defaults to: true)

    whether to print detailed messages

Returns:

  • (Boolean)

    true if safe to switch



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/docopslab/dev/git_branch.rb', line 98

def git_safe_to_switch? branch, verbose: true
  # Check for uncommitted changes
  if git_has_uncommitted_changes?
    puts '❌ You have uncommitted changes. Please commit or stash them first.' if verbose
    puts "💡 Run 'git status' to see changes." if verbose
    return false
  end

  # Check for conflicting untracked files
  conflicts = git_conflicting_files(branch)

  if conflicts.nil?
    puts "❌ Target branch '#{branch}' does not exist." if verbose
    return false
  end

  unless conflicts.empty?
    if verbose
      puts "❌ Untracked files would conflict with branch '#{branch}':"
      conflicts.each { |f| puts "   - #{f}" }
      puts '💡 Commit these files or remove them before switching branches.'
    end
    return false
  end

  true
end

#git_status_summaryHash

Get a summary of git working directory status

Returns:

  • (Hash)

    hash with :branch, :clean, :modified_count, :untracked_count



191
192
193
194
195
196
197
198
# File 'lib/docopslab/dev/git_branch.rb', line 191

def git_status_summary
  {
    branch: git_current_branch,
    clean: !git_has_uncommitted_changes?,
    modified_count: `git status --porcelain`.strip.lines.count,
    untracked_count: git_untracked_files.count
  }
end

#git_untracked_filesArray<String>

Get list of untracked files in working directory

Returns:

  • (Array<String>)

    list of untracked file paths



53
54
55
# File 'lib/docopslab/dev/git_branch.rb', line 53

def git_untracked_files
  `git ls-files --others --exclude-standard`.strip.split("\n")
end