Class: Geet::Utils::GitClient

Inherits:
Object
  • Object
show all
Extended by:
T::Sig
Includes:
Helpers::OsHelper
Defined in:
lib/geet/utils/git_client.rb

Overview

Represents the git program interface; used for performing git operations.

Constant Summary collapse

ORIGIN_NAME =
"origin"
UPSTREAM_NAME =
"upstream"
REMOTE_URL_REGEX =

Simplified, but good enough, pattern.

Relevant matches:

1: protocol + suffix
2: domain
3: domain<>path separator
4: path (repo, project)
5: suffix
%r{
  \A
  (https://|git@)
  (.+?)
  ([/:])
  (.+/.+?)
  (\.git)?
  \Z
}x
REMOTE_BRANCH_REGEX =
%r{\A[^/]+/(.+)\Z}
MAIN_BRANCH_CONFIG_ENTRY =
"custom.development-branch"
CLEAN_TREE_MESSAGE_REGEX =
/^nothing to commit, working tree clean$/

Instance Method Summary collapse

Methods included from Helpers::OsHelper

#execute_command, #open_file_with_default_application

Constructor Details

#initialize(location: nil) ⇒ GitClient

Returns a new instance of GitClient.



46
47
48
# File 'lib/geet/utils/git_client.rb', line 46

def initialize(location: nil)
  @location = location
end

Instance Method Details

#add_remote(name, url) ⇒ Object



289
290
291
# File 'lib/geet/utils/git_client.rb', line 289

def add_remote(name, url)
  execute_git_command("remote add #{name.shellescape} #{url}")
end

#checkout(branch) ⇒ Object



252
253
254
# File 'lib/geet/utils/git_client.rb', line 252

def checkout(branch)
  execute_git_command("checkout #{branch.shellescape}")
end

#cherry(upstream, head: nil) ⇒ Object



64
65
66
67
68
69
70
71
72
73
# File 'lib/geet/utils/git_client.rb', line 64

def cherry(upstream, head: nil)
  upstream = main_branch if upstream == :main_branch
  head = main_branch if head == :main_branch

  git_params = [upstream, head].compact.map { |param| param.to_s.shellescape }

  raw_commits = execute_git_command("cherry #{git_params.join(' ')}")

  raw_commits.split("\n").grep(/^\+/).map { |line| T.must(line[3..-1]) }
end

#current_branchObject



76
77
78
79
80
81
82
# File 'lib/geet/utils/git_client.rb', line 76

def current_branch
  branch = execute_git_command("rev-parse --abbrev-ref HEAD")

  raise "Couldn't find current branch" if branch == "HEAD"

  branch
end

#delete_branch(branch, force:) ⇒ Object



257
258
259
260
261
# File 'lib/geet/utils/git_client.rb', line 257

def delete_branch(branch, force:)
  force_option = "--force" if force

  execute_git_command("branch --delete #{force_option} #{branch.shellescape}")
end

#fetchObject



284
285
286
# File 'lib/geet/utils/git_client.rb', line 284

def fetch
  execute_git_command("fetch --prune")
end

#main_branchObject



136
137
138
139
140
141
142
143
144
145
# File 'lib/geet/utils/git_client.rb', line 136

def main_branch
  branch_name = execute_git_command("config --get #{MAIN_BRANCH_CONFIG_ENTRY}", allow_error: true)

  if branch_name.empty?
    full_branch_name = execute_git_command("rev-parse --abbrev-ref #{ORIGIN_NAME}/HEAD")
    T.must(full_branch_name.split("/").last)
  else
    branch_name
  end
end

#ownerObject



208
209
210
# File 'lib/geet/utils/git_client.rb', line 208

def owner
  T.must(path.split("/")[0])
end

#path(upstream: false) ⇒ Object



201
202
203
204
205
# File 'lib/geet/utils/git_client.rb', line 201

def path(upstream: false)
  remote_name_option = upstream ? {name: UPSTREAM_NAME} : {}

  T.must(remote(**remote_name_option)[REMOTE_URL_REGEX, 4])
end

#push(remote_branch: nil, force: false) ⇒ Object



275
276
277
278
279
# File 'lib/geet/utils/git_client.rb', line 275

def push(remote_branch: nil, force: false)
  remote_branch_option = "-u #{ORIGIN_NAME} #{remote_branch.shellescape}" if remote_branch

  execute_git_command("push #{"--force" if force} #{remote_branch_option}")
end

#rebaseObject



264
265
266
# File 'lib/geet/utils/git_client.rb', line 264

def rebase
  execute_git_command("rebase")
end

#remote(name: nil) ⇒ Object



223
224
225
226
227
228
229
230
231
232
233
# File 'lib/geet/utils/git_client.rb', line 223

def remote(name: nil)
  remote_url = execute_git_command("ls-remote --get-url #{name}")

  if !remote_defined?(name)
    raise "Remote #{name.inspect} not found!"
  elsif remote_url !~ REMOTE_URL_REGEX
    raise "Unexpected remote reference format: #{remote_url.inspect}"
  end

  remote_url
end

#remote_branch(qualify: false) ⇒ Object



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/geet/utils/git_client.rb', line 94

def remote_branch(qualify: false)
  head_symbolic_ref = execute_git_command("symbolic-ref -q HEAD")

  raw_remote_branch = execute_git_command("for-each-ref --format='%(upstream:short)' #{head_symbolic_ref.shellescape}").strip

  if raw_remote_branch != ""
    if qualify
      raw_remote_branch
    else
      raw_remote_branch[REMOTE_BRANCH_REGEX, 1] || raise("Unexpected remote branch format: #{raw_remote_branch}")
    end
  else
    nil
  end
end

#remote_branch_diffObject



157
158
159
160
161
# File 'lib/geet/utils/git_client.rb', line 157

def remote_branch_diff
  remote_branch = T.must(remote_branch(qualify: true))

  execute_git_command("diff #{remote_branch.shellescape}")
end

#remote_branch_diff_commitsObject



150
151
152
153
154
# File 'lib/geet/utils/git_client.rb', line 150

def remote_branch_diff_commits
  remote_branch = T.must(remote_branch(qualify: true))

  execute_git_command("rev-list #{remote_branch.shellescape}..HEAD")
end

#remote_branch_gone?Boolean

Returns:

  • (Boolean)


119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/geet/utils/git_client.rb', line 119

def remote_branch_gone?
  git_command = "status -b --porcelain"
  status_output = execute_git_command(git_command)

  # Simplified branch naming pattern. The exact one (see https://stackoverflow.com/a/3651867)
  # is not worth implementing.
  #
  if status_output =~ %r(^## .+\.\.\..+?( \[gone\])?$)
    !!$LAST_MATCH_INFO[1]
  else
    raise "Unexpected git command #{git_command.inspect} output: #{status_output}"
  end
end

#remote_components(name: nil) ⇒ Object



194
195
196
# File 'lib/geet/utils/git_client.rb', line 194

def remote_components(name: nil)
  T.must(remote.match(REMOTE_URL_REGEX))[1..]
end

#remote_defined?(name) ⇒ Boolean

Returns:

  • (Boolean)


239
240
241
242
243
244
245
# File 'lib/geet/utils/git_client.rb', line 239

def remote_defined?(name)
  remote_url = execute_git_command("ls-remote --get-url #{name}")

  # If the remote is not defined, `git ls-remote` will return the passed value.
  #
  remote_url != name
end

#show_description(object) ⇒ Object



177
178
179
# File 'lib/geet/utils/git_client.rb', line 177

def show_description(object)
  execute_git_command("show --quiet --format='%s\n\n%b' #{object.shellescape}")
end

#working_tree_clean?Boolean

Returns:

  • (Boolean)


164
165
166
167
168
# File 'lib/geet/utils/git_client.rb', line 164

def working_tree_clean?
  git_message = execute_git_command("status")

  !!(git_message =~ CLEAN_TREE_MESSAGE_REGEX)
end