Class: Geet::Utils::GitClient

Inherits:
Object
  • Object
show all
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

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
UPSTREAM_BRANCH_REGEX =
%r{\A[^/]+/([^/]+)\Z}
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.



40
41
42
# File 'lib/geet/utils/git_client.rb', line 40

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

Instance Method Details

#add_remote(name, url) ⇒ Object



217
218
219
# File 'lib/geet/utils/git_client.rb', line 217

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

#checkout(branch) ⇒ Object

OPERATION APIS



189
190
191
# File 'lib/geet/utils/git_client.rb', line 189

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

#cherry(limit) ⇒ Object

Return the commit shas between HEAD and limit, excluding the already applied commits (which start with -)



51
52
53
54
55
# File 'lib/geet/utils/git_client.rb', line 51

def cherry(limit)
  raw_commits = execute_git_command("cherry #{limit.shellescape}")

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

#current_branchObject



57
58
59
60
61
62
63
# File 'lib/geet/utils/git_client.rb', line 57

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) ⇒ Object

Unforced deletion.



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

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

#fetchObject

Performs pruning.



213
214
215
# File 'lib/geet/utils/git_client.rb', line 213

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

#ownerObject



140
141
142
# File 'lib/geet/utils/git_client.rb', line 140

def owner
  path.split('/')[0]
end

#path(upstream: false) ⇒ Object

Example: donaldduck/geet



134
135
136
137
138
# File 'lib/geet/utils/git_client.rb', line 134

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

  remote(**remote_name_option)[REMOTE_URL_REGEX, 4]
end

#provider_domainObject



144
145
146
147
148
149
150
151
152
# File 'lib/geet/utils/git_client.rb', line 144

def provider_domain
  # We assume that it's not possible to have origin and upstream on different providers.

  domain = remote()[REMOTE_URL_REGEX, 2]

  raise "Can't identify domain in the provider domain string: #{domain}" if domain !~ /\w+\.\w+/

  domain
end

#push(upstream_branch: nil) ⇒ Object

upstream_branch: create an upstream branch.



205
206
207
208
209
# File 'lib/geet/utils/git_client.rb', line 205

def push(upstream_branch: nil)
  upstream_branch_option = "-u origin #{upstream_branch.shellescape}" if upstream_branch

  execute_git_command("push #{upstream_branch_option}")
end

#rebaseObject



199
200
201
# File 'lib/geet/utils/git_client.rb', line 199

def rebase
  execute_git_command("rebase")
end

#remote(name: nil) ⇒ Object

Returns the URL of the remote with the given name. Sanity checks are performed.

The result is in the format ‘[email protected]:donaldduck/geet.git`

options

:name:           remote name; if unspecified, the default remote is used.


162
163
164
165
166
167
168
169
170
171
172
# File 'lib/geet/utils/git_client.rb', line 162

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_components(name: nil) ⇒ Object

Return the components of the remote, according to REMOTE_URL_REGEX; doesn’t include the full match.



128
129
130
# File 'lib/geet/utils/git_client.rb', line 128

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

#remote_defined?(name) ⇒ Boolean

Doesn’t sanity check for the remote url format; this action is for querying purposes, any any action that needs to work with the remote, uses #remote.

Returns:

  • (Boolean)


177
178
179
180
181
182
183
# File 'lib/geet/utils/git_client.rb', line 177

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

Show the description (“<subject>nn<body>”) for the given git object.



117
118
119
# File 'lib/geet/utils/git_client.rb', line 117

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

#upstream_branchObject

Not to be confused with upstream repository!

This API doesn’t reveal if the remote branch is gone.

return: nil, if the upstream branch is not configured.



71
72
73
74
75
76
77
78
79
80
81
# File 'lib/geet/utils/git_client.rb', line 71

def upstream_branch
  head_symbolic_ref = execute_git_command("symbolic-ref -q HEAD")

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

  if raw_upstream_branch != ''
    raw_upstream_branch[UPSTREAM_BRANCH_REGEX, 1] || raise("Unexpected upstream format: #{raw_upstream_branch}")
  else
    nil
  end
end

#upstream_branch_gone?Boolean

TODO: May be merged with :upstream_branch, although it would require designing how a gone remote branch is expressed.

Sample command output:

## add_milestone_closing...origin/add_milestone_closing [gone]
 M spec/integration/merge_pr_spec.rb

Returns:

  • (Boolean)


91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/geet/utils/git_client.rb', line 91

def upstream_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

#working_tree_clean?Boolean

Returns:

  • (Boolean)


105
106
107
108
109
# File 'lib/geet/utils/git_client.rb', line 105

def working_tree_clean?
  git_message = execute_git_command("status")

  !!(git_message =~ CLEAN_TREE_MESSAGE_REGEX)
end