Class: Kettle::Dev::GitAdapter

Inherits:
Object
  • Object
show all
Defined in:
lib/kettle/dev/git_adapter.rb

Overview

Minimal Git adapter used by kettle-dev to avoid invoking live shell commands
directly from the higher-level library code. In tests, mock this adapter’s
methods to prevent any real network or repository mutations.

Behavior:

  • Prefer the ‘git’ gem when available.
  • If the ‘git’ gem is not present (LoadError), fall back to shelling out to
    the system git executable for the small set of operations we need.

Public API is intentionally small and only includes what we need right now.

Instance Method Summary collapse

Constructor Details

#initializevoid

Create a new adapter rooted at the current working directory.



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/kettle/dev/git_adapter.rb', line 33

def initialize
  begin
    # Allow users/CI to opt out of using the 'git' gem even when available.
    # Set KETTLE_DEV_DISABLE_GIT_GEM to a truthy value ("1", "true", "yes") to force CLI backend.
    env_val = ENV["KETTLE_DEV_DISABLE_GIT_GEM"]
    # Ruby 2.3 compatibility: String#match? was added in 2.4; use Regexp#=== / =~ instead
    disable_gem = env_val && !!(/\A(1|true|yes)\z/i =~ env_val)
    if disable_gem
      @backend = :cli
    else
      Kernel.require "git"
      @backend = :gem
      @git = ::Git.open(Dir.pwd)
    end
  rescue LoadError
    # Optional dependency: fall back to CLI
    @backend = :cli
  rescue StandardError => e
    raise Kettle::Dev::Error, "Failed to open git repository: #{e.message}"
  end
end

Instance Method Details

#capture(args) ⇒ Array<(String, Boolean)>

Execute a git command and capture its stdout and success flag.
This is a generic escape hatch used by higher-level code for read-only
queries that aren’t covered by the explicit adapter API. Tests can stub
this method to avoid shelling out.

Parameters:

  • args (Array<String>)

Returns:

  • (Array<(String, Boolean)>)

    [output, success]



24
25
26
27
28
29
# File 'lib/kettle/dev/git_adapter.rb', line 24

def capture(args)
  out, status = Open3.capture2("git", *args)
  [out.strip, status.success?]
rescue StandardError
  ["", false]
end

#checkout(branch) ⇒ Boolean

Checkout the given branch

Parameters:

  • branch (String)

Returns:

  • (Boolean)


150
151
152
153
154
155
156
157
158
159
# File 'lib/kettle/dev/git_adapter.rb', line 150

def checkout(branch)
  if @backend == :gem
    @git.checkout(branch)
    true
  else
    system("git", "checkout", branch.to_s)
  end
rescue StandardError
  false
end

#current_branchString?

Returns current branch name, or nil on error.

Returns:

  • (String, nil)

    current branch name, or nil on error



84
85
86
87
88
89
90
91
92
93
# File 'lib/kettle/dev/git_adapter.rb', line 84

def current_branch
  if @backend == :gem
    @git.current_branch
  else
    out, status = Open3.capture2("git", "rev-parse", "--abbrev-ref", "HEAD")
    status.success? ? out.strip : nil
  end
rescue StandardError
  nil
end

#fetch(remote, ref = nil) ⇒ Boolean

Fetch a ref from a remote (or everything if ref is nil)

Parameters:

  • remote (String)
  • ref (String, nil) (defaults to: nil)

Returns:

  • (Boolean)


180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/kettle/dev/git_adapter.rb', line 180

def fetch(remote, ref = nil)
  if @backend == :gem
    if ref
      @git.fetch(remote, ref)
    else
      @git.fetch(remote)
    end
    true
  elsif ref
    system("git", "fetch", remote.to_s, ref.to_s)
  else
    system("git", "fetch", remote.to_s)
  end
rescue StandardError
  false
end

#pull(remote, branch) ⇒ Boolean

Pull from a remote/branch

Parameters:

  • remote (String)
  • branch (String)

Returns:

  • (Boolean)


165
166
167
168
169
170
171
172
173
174
# File 'lib/kettle/dev/git_adapter.rb', line 165

def pull(remote, branch)
  if @backend == :gem
    @git.pull(remote, branch)
    true
  else
    system("git", "pull", remote.to_s, branch.to_s)
  end
rescue StandardError
  false
end

#push(remote, branch, force: false) ⇒ Boolean

Push a branch to a remote.

Parameters:

  • remote (String, nil)

    remote name (nil means default remote)

  • branch (String)

    branch name (required)

  • force (Boolean) (defaults to: false)

    whether to force push

Returns:

  • (Boolean)

    true when the push is reported successful



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/kettle/dev/git_adapter.rb', line 60

def push(remote, branch, force: false)
  if @backend == :gem
    begin
      if remote
        @git.push(remote, branch, force: force)
      else
        # Default remote according to repo config
        @git.push(nil, branch, force: force)
      end
      true
    rescue StandardError
      false
    end
  else
    args = ["git", "push"]
    args << "--force" if force
    if remote
      args << remote.to_s << branch.to_s
    end
    system(*args)
  end
end

#remote_url(name) ⇒ String?

Parameters:

  • name (String)

Returns:

  • (String, nil)


135
136
137
138
139
140
141
142
143
144
145
# File 'lib/kettle/dev/git_adapter.rb', line 135

def remote_url(name)
  if @backend == :gem
    r = @git.remotes.find { |x| x.name == name }
    r&.url
  else
    out, status = Open3.capture2("git", "config", "--get", "remote.#{name}.url")
    status.success? ? out.strip : nil
  end
rescue StandardError
  nil
end

#remotesArray<String>

Returns list of remote names.

Returns:

  • (Array<String>)

    list of remote names



96
97
98
99
100
101
102
103
104
105
# File 'lib/kettle/dev/git_adapter.rb', line 96

def remotes
  if @backend == :gem
    @git.remotes.map(&:name)
  else
    out, status = Open3.capture2("git", "remote")
    status.success? ? out.split(/\r?\n/).map(&:strip).reject(&:empty?) : []
  end
rescue StandardError
  []
end

#remotes_with_urlsHash{String=>String}

Returns remote name => fetch URL.

Returns:

  • (Hash{String=>String})

    remote name => fetch URL



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/kettle/dev/git_adapter.rb', line 108

def remotes_with_urls
  if @backend == :gem
    @git.remotes.each_with_object({}) do |r, h|
      begin
        h[r.name] = r.url
      rescue StandardError
        # ignore
      end
    end
  else
    out, status = Open3.capture2("git", "remote", "-v")
    return {} unless status.success?
    urls = {}
    out.each_line do |line|
      # Example: origin https://github.com/me/repo.git (fetch)
      if line =~ /^(\S+)\s+(\S+)\s+\(fetch\)/
        urls[Regexp.last_match(1)] = Regexp.last_match(2)
      end
    end
    urls
  end
rescue StandardError
  {}
end