Class: Geet::Utils::GitClient
- Inherits:
-
Object
- Object
- Geet::Utils::GitClient
- 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
- #add_remote(name, url) ⇒ Object
-
#checkout(branch) ⇒ Object
OPERATION APIS.
-
#cherry(upstream, head: nil) ⇒ Object
Return the commit SHAs between :head and :upstream, excluding the already applied commits (which start with ‘-`).
- #current_branch ⇒ Object
- #delete_branch(branch, force:) ⇒ Object
-
#fetch ⇒ Object
Performs pruning.
-
#initialize(location: nil) ⇒ GitClient
constructor
A new instance of GitClient.
- #main_branch ⇒ Object
- #owner ⇒ Object
-
#path(upstream: false) ⇒ Object
Example: ‘donaldduck/geet`.
- #provider_domain ⇒ Object
-
#push(remote_branch: nil, force: false) ⇒ Object
remote_branch: create an upstream branch.
- #rebase ⇒ Object
-
#remote(name: nil) ⇒ Object
Returns the URL of the remote with the given name.
-
#remote_branch(qualify: false) ⇒ Object
This API doesn’t reveal if the remote branch is gone.
- #remote_branch_diff ⇒ Object
-
#remote_branch_diff_commits ⇒ Object
List of different commits between local and corresponding remote branch.
-
#remote_branch_gone? ⇒ Boolean
TODO: May be merged with :remote_branch, although it would require designing how a gone remote branch is expressed.
-
#remote_components(name: nil) ⇒ Object
Return the components of the remote, according to REMOTE_URL_REGEX; doesn’t include the full match.
-
#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.
-
#show_description(object) ⇒ Object
Show the description (“<subject>nn<body>”) for the given git object.
- #working_tree_clean? ⇒ Boolean
Methods included from Helpers::OsHelper
#execute_command, #open_file_with_default_application
Constructor Details
#initialize(location: nil) ⇒ GitClient
Returns a new instance of GitClient.
43 44 45 |
# File 'lib/geet/utils/git_client.rb', line 43 def initialize(location: nil) @location = location end |
Instance Method Details
#add_remote(name, url) ⇒ Object
259 260 261 |
# File 'lib/geet/utils/git_client.rb', line 259 def add_remote(name, url) execute_git_command("remote add #{name.shellescape} #{url}") end |
#checkout(branch) ⇒ Object
OPERATION APIS
231 232 233 |
# File 'lib/geet/utils/git_client.rb', line 231 def checkout(branch) execute_git_command("checkout #{branch.shellescape}") end |
#cherry(upstream, head: nil) ⇒ Object
Return the commit SHAs between :head and :upstream, excluding the already applied commits (which start with ‘-`)
-
upstream: String; pass :main_branch to use the main branch
-
head: String (optional); pass :main_branch to use the main branch
57 58 59 60 61 62 63 64 65 66 |
# File 'lib/geet/utils/git_client.rb', line 57 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(&:shellescape) raw_commits = execute_git_command("cherry #{git_params.join(' ')}") raw_commits.split("\n").grep(/^\+/).map { |line| line[3..-1] } end |
#current_branch ⇒ Object
68 69 70 71 72 73 74 |
# File 'lib/geet/utils/git_client.rb', line 68 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
235 236 237 238 239 |
# File 'lib/geet/utils/git_client.rb', line 235 def delete_branch(branch, force:) force_option = "--force" if force execute_git_command("branch --delete #{force_option} #{branch.shellescape}") end |
#fetch ⇒ Object
Performs pruning.
255 256 257 |
# File 'lib/geet/utils/git_client.rb', line 255 def fetch execute_git_command("fetch --prune") end |
#main_branch ⇒ Object
122 123 124 125 126 127 128 129 130 131 |
# File 'lib/geet/utils/git_client.rb', line 122 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") full_branch_name.split('/').last else branch_name end end |
#owner ⇒ Object
182 183 184 |
# File 'lib/geet/utils/git_client.rb', line 182 def owner path.split('/')[0] end |
#path(upstream: false) ⇒ Object
Example: ‘donaldduck/geet`
176 177 178 179 180 |
# File 'lib/geet/utils/git_client.rb', line 176 def path(upstream: false) remote_name_option = upstream ? {name: UPSTREAM_NAME} : {} remote(**remote_name_option)[REMOTE_URL_REGEX, 4] end |
#provider_domain ⇒ Object
186 187 188 189 190 191 192 193 194 |
# File 'lib/geet/utils/git_client.rb', line 186 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(remote_branch: nil, force: false) ⇒ Object
remote_branch: create an upstream branch.
247 248 249 250 251 |
# File 'lib/geet/utils/git_client.rb', line 247 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 |
#rebase ⇒ Object
241 242 243 |
# File 'lib/geet/utils/git_client.rb', line 241 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.
204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/geet/utils/git_client.rb', line 204 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
This API doesn’t reveal if the remote branch is gone.
qualify: (false) include the remote if true, don’t otherwise
return: nil, if the remote branch is not configured.
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/geet/utils/git_client.rb', line 82 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_diff ⇒ Object
141 142 143 144 145 |
# File 'lib/geet/utils/git_client.rb', line 141 def remote_branch_diff remote_branch = remote_branch(qualify: true) execute_git_command("diff #{remote_branch.shellescape}") end |
#remote_branch_diff_commits ⇒ Object
List of different commits between local and corresponding remote branch.
135 136 137 138 139 |
# File 'lib/geet/utils/git_client.rb', line 135 def remote_branch_diff_commits remote_branch = remote_branch(qualify: true) execute_git_command("rev-list #{remote_branch.shellescape}..HEAD") end |
#remote_branch_gone? ⇒ Boolean
TODO: May be merged with :remote_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
106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/geet/utils/git_client.rb', line 106 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
Return the components of the remote, according to REMOTE_URL_REGEX; doesn’t include the full match.
170 171 172 |
# File 'lib/geet/utils/git_client.rb', line 170 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.
219 220 221 222 223 224 225 |
# File 'lib/geet/utils/git_client.rb', line 219 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.
159 160 161 |
# File 'lib/geet/utils/git_client.rb', line 159 def show_description(object) execute_git_command("show --quiet --format='%s\n\n%b' #{object.shellescape}") end |
#working_tree_clean? ⇒ Boolean
147 148 149 150 151 |
# File 'lib/geet/utils/git_client.rb', line 147 def working_tree_clean? = execute_git_command("status") !!( =~ CLEAN_TREE_MESSAGE_REGEX) end |