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(base: nil) ⇒ Object
Return the commit SHAs between HEAD and ‘base`, excluding the already applied commits (which start with `-`).
- #current_branch ⇒ Object
-
#delete_branch(branch) ⇒ Object
Unforced deletion.
-
#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
253 254 255 |
# File 'lib/geet/utils/git_client.rb', line 253 def add_remote(name, url) execute_git_command("remote add #{name.shellescape} #{url}") end |
#checkout(branch) ⇒ Object
OPERATION APIS
225 226 227 |
# File 'lib/geet/utils/git_client.rb', line 225 def checkout(branch) execute_git_command("checkout #{branch.shellescape}") end |
#cherry(base: nil) ⇒ Object
Return the commit SHAs between HEAD and ‘base`, excluding the already applied commits (which start with `-`)
54 55 56 57 58 59 60 |
# File 'lib/geet/utils/git_client.rb', line 54 def cherry(base: nil) base ||= main_branch raw_commits = execute_git_command("cherry #{base.shellescape}") raw_commits.split("\n").grep(/^\+/).map { |line| line[3..-1] } end |
#current_branch ⇒ Object
62 63 64 65 66 67 68 |
# File 'lib/geet/utils/git_client.rb', line 62 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.
231 232 233 |
# File 'lib/geet/utils/git_client.rb', line 231 def delete_branch(branch) execute_git_command("branch --delete #{branch.shellescape}") end |
#fetch ⇒ Object
Performs pruning.
249 250 251 |
# File 'lib/geet/utils/git_client.rb', line 249 def fetch execute_git_command("fetch --prune") end |
#main_branch ⇒ Object
116 117 118 119 120 121 122 123 124 125 |
# File 'lib/geet/utils/git_client.rb', line 116 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
176 177 178 |
# File 'lib/geet/utils/git_client.rb', line 176 def owner path.split('/')[0] end |
#path(upstream: false) ⇒ Object
Example: ‘donaldduck/geet`
170 171 172 173 174 |
# File 'lib/geet/utils/git_client.rb', line 170 def path(upstream: false) remote_name_option = upstream ? {name: UPSTREAM_NAME} : {} remote(**remote_name_option)[REMOTE_URL_REGEX, 4] end |
#provider_domain ⇒ Object
180 181 182 183 184 185 186 187 188 |
# File 'lib/geet/utils/git_client.rb', line 180 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.
241 242 243 244 245 |
# File 'lib/geet/utils/git_client.rb', line 241 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
235 236 237 |
# File 'lib/geet/utils/git_client.rb', line 235 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.
198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/geet/utils/git_client.rb', line 198 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.
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/geet/utils/git_client.rb', line 76 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
135 136 137 138 139 |
# File 'lib/geet/utils/git_client.rb', line 135 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.
129 130 131 132 133 |
# File 'lib/geet/utils/git_client.rb', line 129 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
100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/geet/utils/git_client.rb', line 100 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.
164 165 166 |
# File 'lib/geet/utils/git_client.rb', line 164 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.
213 214 215 216 217 218 219 |
# File 'lib/geet/utils/git_client.rb', line 213 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.
153 154 155 |
# File 'lib/geet/utils/git_client.rb', line 153 def show_description(object) execute_git_command("show --quiet --format='%s\n\n%b' #{object.shellescape}") end |
#working_tree_clean? ⇒ Boolean
141 142 143 144 145 |
# File 'lib/geet/utils/git_client.rb', line 141 def working_tree_clean? = execute_git_command("status") !!( =~ CLEAN_TREE_MESSAGE_REGEX) end |