Class: GitReview::Local
- Inherits:
-
Object
- Object
- GitReview::Local
- Includes:
- Helpers
- Defined in:
- lib/git-review/local.rb
Overview
The local repository is where the git-review command is being called by default. It is (supposedly) able to handle systems other than Github. TODO: remove Github-dependency
Instance Attribute Summary collapse
-
#config ⇒ Object
Returns the value of attribute config.
Class Method Summary collapse
-
.instance ⇒ Object
acts like a singleton class but it’s actually not use ::GitReview::Local.instance everywhere except in tests.
Instance Method Summary collapse
-
#all_branches ⇒ Array<String>
All existing branches.
-
#branch_exists?(location, branch_name) ⇒ Boolean
Whether a branch exists in a specified location.
-
#clean_all ⇒ Object
clean all obsolete branches.
-
#clean_remotes ⇒ Object
Remove obsolete remotes with review prefix.
-
#clean_single(number, force = false) ⇒ Object
clean a single request’s obsolete branch.
- #config_list ⇒ Object
-
#create_title_and_body(target_branch) ⇒ Array(String, String)
The title and the body of pull request.
-
#delete_branch(branch_name) ⇒ Object
delete local and remote branches that match a given name.
-
#delete_local_branch(branch_name) ⇒ Object
delete local branch if it exists.
-
#delete_remote_branch(branch_name) ⇒ Object
delete remote branch if it exists.
-
#edit_title_and_body(title, body) ⇒ Object
TODO: refactor.
-
#head ⇒ String
The head string used for pull requests.
-
#initialize ⇒ Local
constructor
A new instance of Local.
- #load_config ⇒ Object
-
#merged?(sha) ⇒ Boolean
Whether a specified commit has already been merged.
-
#new_commits?(upstream = false) ⇒ Boolean
Whether there are commits not in target branch yet.
-
#on_feature_branch? ⇒ Boolean
Whether already on a feature branch.
-
#protected_branches ⇒ Array<String>
All open requests’ branches shouldn’t be deleted.
-
#prune_remotes ⇒ Object
Prune all configured remotes.
-
#remote_exists?(name) ⇒ Boolean
Determine whether a remote with a given name exists?.
-
#remote_for_branch(branch_name) ⇒ Object
Finds the correct remote for a given branch name.
-
#remote_for_request(request) ⇒ Object
Find or create the correct remote for a fork with a given owner name.
-
#remotes ⇒ Object
List all available remotes.
-
#remotes_for_branches ⇒ Object
Find all remotes which are currently referenced by local branches.
-
#remotes_for_url(remote_url) ⇒ Object
Collect all remotes for a given url.
-
#remotes_with_urls ⇒ Object
Create a Hash with all remotes as keys and their urls as values.
-
#review_branches ⇒ Array<String>
All review branches with ‘review_’ prefix.
-
#sanitize_branch_name(name) ⇒ Object
Remove all non word characters and turn them into underscores.
- #server ⇒ Object
-
#source ⇒ String
Combine source repo and branch.
-
#source_branch ⇒ String
The current source branch.
-
#source_repo ⇒ String
The source repo.
-
#target ⇒ String
Combine target repo and branch.
-
#target_branch ⇒ String
The name of the target branch.
-
#target_repo(upstream = false) ⇒ String
if to send a pull request to upstream repo, get the parent as target.
-
#uncommitted_changes? ⇒ Boolean
Whether there are local changes not committed.
-
#unmerged_commits?(branch_name, verbose = true) ⇒ Boolean
Whether there are unmerged commits on the local or remote branch.
Constructor Details
#initialize ⇒ Local
Returns a new instance of Local.
18 19 20 21 22 23 24 25 |
# File 'lib/git-review/local.rb', line 18 def initialize # find root git directory if currently in subdirectory if git_call('rev-parse --show-toplevel').strip.empty? raise ::GitReview::InvalidGitRepositoryError else load_config end end |
Instance Attribute Details
#config ⇒ Object
Returns the value of attribute config.
10 11 12 |
# File 'lib/git-review/local.rb', line 10 def config @config end |
Class Method Details
.instance ⇒ Object
acts like a singleton class but it’s actually not use ::GitReview::Local.instance everywhere except in tests
14 15 16 |
# File 'lib/git-review/local.rb', line 14 def self.instance @instance ||= new end |
Instance Method Details
#all_branches ⇒ Array<String>
Returns all existing branches.
111 112 113 |
# File 'lib/git-review/local.rb', line 111 def all_branches git_call('branch -a').gsub('* ', '').split("\n").collect { |s| s.strip } end |
#branch_exists?(location, branch_name) ⇒ Boolean
Returns whether a branch exists in a specified location.
180 181 182 183 184 |
# File 'lib/git-review/local.rb', line 180 def branch_exists?(location, branch_name) return false unless [:remote, :local].include?(location) prefix = location == :remote ? 'remotes/origin/' : '' all_branches.include?(prefix + branch_name) end |
#clean_all ⇒ Object
clean all obsolete branches
147 148 149 150 151 152 |
# File 'lib/git-review/local.rb', line 147 def clean_all (review_branches - protected_branches).each do |branch_name| # only clean up obsolete branches. delete_branch(branch_name) unless unmerged_commits?(branch_name, false) end end |
#clean_remotes ⇒ Object
Remove obsolete remotes with review prefix.
75 76 77 78 79 80 81 82 83 |
# File 'lib/git-review/local.rb', line 75 def clean_remotes protected_remotes = remotes_for_branches remotes.each do |remote| # Only remove review remotes that aren't referenced by current branches. if remote.index('review_') == 0 && !protected_remotes.include?(remote) git_call "remote remove #{remote}" end end end |
#clean_single(number, force = false) ⇒ Object
clean a single request’s obsolete branch
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/git-review/local.rb', line 130 def clean_single(number, force = false) request = server.pull_request(source_repo, number) if request && request.state == 'closed' # ensure there are no unmerged commits or '--force' flag has been set branch_name = request.head.ref if unmerged_commits?(branch_name) && !force puts "Won't delete branches that contain unmerged commits." puts "Use '--force' to override." else delete_branch(branch_name) end end rescue Octokit::NotFound false end |
#config_list ⇒ Object
313 314 315 |
# File 'lib/git-review/local.rb', line 313 def config_list git_call('config --list', false) end |
#create_title_and_body(target_branch) ⇒ Array(String, String)
Returns the title and the body of pull request.
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'lib/git-review/local.rb', line 322 def create_title_and_body(target_branch) login = server.login commits = git_call("log --format='%H' HEAD...#{target_branch}"). lines.count puts "Commits: #{commits}" if commits == 1 # we can create a really specific title and body title = git_call("log --format='%s' HEAD...#{target_branch}").chomp body = git_call("log --format='%b' HEAD...#{target_branch}").chomp else title = "[Review] Request from '#{login}' @ '#{source}'" body = "Please review the following changes:\n" body += git_call("log --oneline HEAD...#{target_branch}"). lines.map{|l| " * #{l.chomp}"}.join("\n") end edit_title_and_body(title, body) end |
#delete_branch(branch_name) ⇒ Object
delete local and remote branches that match a given name
156 157 158 159 |
# File 'lib/git-review/local.rb', line 156 def delete_branch(branch_name) delete_local_branch(branch_name) delete_remote_branch(branch_name) end |
#delete_local_branch(branch_name) ⇒ Object
delete local branch if it exists.
163 164 165 166 167 |
# File 'lib/git-review/local.rb', line 163 def delete_local_branch(branch_name) if branch_exists?(:local, branch_name) git_call("branch -D #{branch_name}", true) end end |
#delete_remote_branch(branch_name) ⇒ Object
delete remote branch if it exists.
171 172 173 174 175 |
# File 'lib/git-review/local.rb', line 171 def delete_remote_branch(branch_name) if branch_exists?(:remote, branch_name) git_call("push origin :#{branch_name}", true) end end |
#edit_title_and_body(title, body) ⇒ Object
TODO: refactor
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 |
# File 'lib/git-review/local.rb', line 341 def edit_title_and_body(title, body) tmpfile = Tempfile.new('git-review') tmpfile.write(title + "\n\n" + body) tmpfile.flush editor = ENV['TERM_EDITOR'] || ENV['EDITOR'] unless editor warn 'Please set $EDITOR or $TERM_EDITOR in your .bash_profile.' end system("#{editor || 'open'} #{tmpfile.path}") tmpfile.rewind lines = tmpfile.read.lines.to_a #puts lines.inspect title = lines.shift.chomp lines.shift if lines[0].chomp.empty? body = lines.join tmpfile.unlink [title, body] end |
#head ⇒ String
Returns the head string used for pull requests.
282 283 284 285 |
# File 'lib/git-review/local.rb', line 282 def head # in the form of 'user:branch' "#{source_repo.split('/').first}:#{source_branch}" end |
#load_config ⇒ Object
300 301 302 303 304 305 306 307 308 309 310 311 |
# File 'lib/git-review/local.rb', line 300 def load_config @config = {} config_list.split("\n").each do |line| key, value = line.split(/=/, 2) if @config[key] && @config[key] != value @config[key] = [@config[key]].flatten << value else @config[key] = value end end @config end |
#merged?(sha) ⇒ Boolean
Returns whether a specified commit has already been merged.
238 239 240 241 242 |
# File 'lib/git-review/local.rb', line 238 def merged?(sha) branches = git_call("branch --contains #{sha} 2>&1").split("\n"). collect { |b| b.delete('*').strip } branches.include?(target_branch) end |
#new_commits?(upstream = false) ⇒ Boolean
Returns whether there are commits not in target branch yet.
224 225 226 227 228 229 230 231 232 233 234 235 |
# File 'lib/git-review/local.rb', line 224 def new_commits?(upstream = false) # Check if an upstream remote exists and create it if necessary. remote_url = server.remote_url_for(*target_repo(upstream).split('/')) remote = remotes_for_url(remote_url).first unless remote remote = 'upstream' git_call "remote add #{remote} #{remote_url}" end git_call "fetch #{remote}" target = upstream ? "#{remote}/#{target_branch}" : target_branch not git_call("cherry #{target}").empty? end |
#on_feature_branch? ⇒ Boolean
Returns whether already on a feature branch.
288 289 290 291 292 293 |
# File 'lib/git-review/local.rb', line 288 def on_feature_branch? # If current and target are the same, we are not on a feature branch. # If they are different, but we are on master, we should still to switch # to a separate branch (since master makes for a poor feature branch). source_branch != target_branch && source_branch != 'master' end |
#protected_branches ⇒ Array<String>
Returns all open requests’ branches shouldn’t be deleted.
116 117 118 |
# File 'lib/git-review/local.rb', line 116 def protected_branches server.current_requests.collect { |r| r.head.ref } end |
#prune_remotes ⇒ Object
Prune all configured remotes.
86 87 88 |
# File 'lib/git-review/local.rb', line 86 def prune_remotes remotes.each { |remote| git_call "remote prune #{remote}" } end |
#remote_exists?(name) ⇒ Boolean
Determine whether a remote with a given name exists?
33 34 35 |
# File 'lib/git-review/local.rb', line 33 def remote_exists?(name) remotes.include? name end |
#remote_for_branch(branch_name) ⇒ Object
Finds the correct remote for a given branch name.
99 100 101 102 103 104 105 106 107 108 |
# File 'lib/git-review/local.rb', line 99 def remote_for_branch(branch_name) git_call('branch -lvv').gsub('* ', '').split("\n").each do |line| entries = line.split(' ') next unless entries.first == branch_name # Return the remote name or nil for local branches. match = entries[2].match(%r(\[(.*)(\]|:))) return match[1].split('/').first if match end nil end |
#remote_for_request(request) ⇒ Object
Find or create the correct remote for a fork with a given owner name.
61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/git-review/local.rb', line 61 def remote_for_request(request) repo_owner = request.head.repo.owner.login remote_url = server.remote_url_for(repo_owner) remotes = remotes_for_url(remote_url) if remotes.empty? remote = "review_#{repo_owner}" git_call("remote add #{remote} #{remote_url}", debug_mode, true) else remote = remotes.first end remote end |
#remotes ⇒ Object
List all available remotes.
28 29 30 |
# File 'lib/git-review/local.rb', line 28 def remotes git_call('remote').split("\n") end |
#remotes_for_branches ⇒ Object
Find all remotes which are currently referenced by local branches.
91 92 93 94 95 96 |
# File 'lib/git-review/local.rb', line 91 def remotes_for_branches remotes = git_call('branch -lvv').gsub('* ', '').split("\n").map do |line| line.split(' ')[2][1..-2].split('/').first end remotes.uniq end |
#remotes_for_url(remote_url) ⇒ Object
Collect all remotes for a given url.
53 54 55 56 57 58 |
# File 'lib/git-review/local.rb', line 53 def remotes_for_url(remote_url) result = remotes_with_urls.collect do |remote, urls| remote if urls.values.all? { |url| url == remote_url } end result.compact end |
#remotes_with_urls ⇒ Object
Create a Hash with all remotes as keys and their urls as values.
38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/git-review/local.rb', line 38 def remotes_with_urls result = {} git_call('remote -vv').split("\n").each do |line| entries = line.split("\t") remote = entries.first target_entry = entries.last.split(' ') direction = target_entry.last[1..-2].to_sym target_url = target_entry.first result[remote] ||= {} result[remote][direction] = target_url end result end |
#review_branches ⇒ Array<String>
Returns all review branches with ‘review_’ prefix.
121 122 123 124 125 126 127 |
# File 'lib/git-review/local.rb', line 121 def review_branches all_branches.collect { |entry| # only use uniq branch names (no matter if local or remote) branch_name = entry.split('/').last branch_name if branch_name.index('review_') == 0 }.compact.uniq end |
#sanitize_branch_name(name) ⇒ Object
Remove all non word characters and turn them into underscores.
296 297 298 |
# File 'lib/git-review/local.rb', line 296 def sanitize_branch_name(name) name.gsub(/\W+/, '_').downcase end |
#server ⇒ Object
317 318 319 |
# File 'lib/git-review/local.rb', line 317 def server @server ||= ::GitReview::Server.instance end |
#source ⇒ String
Returns combine source repo and branch.
255 256 257 |
# File 'lib/git-review/local.rb', line 255 def source "#{source_repo}/#{source_branch}" end |
#source_branch ⇒ String
Returns the current source branch.
250 251 252 |
# File 'lib/git-review/local.rb', line 250 def source_branch git_call('branch').chomp.match(/\*(.*)/)[0][2..-1] end |
#source_repo ⇒ String
Returns the source repo.
245 246 247 |
# File 'lib/git-review/local.rb', line 245 def source_repo server.source_repo end |
#target ⇒ String
Returns combine target repo and branch.
277 278 279 |
# File 'lib/git-review/local.rb', line 277 def target "#{target_repo}/#{target_branch}" end |
#target_branch ⇒ String
Returns the name of the target branch.
260 261 262 263 |
# File 'lib/git-review/local.rb', line 260 def target_branch # TODO: Manually override this and set arbitrary branches ENV['TARGET_BRANCH'] || 'master' end |
#target_repo(upstream = false) ⇒ String
if to send a pull request to upstream repo, get the parent as target
267 268 269 270 271 272 273 274 |
# File 'lib/git-review/local.rb', line 267 def target_repo(upstream=false) # TODO: Manually override this and set arbitrary repositories if upstream server.repository(source_repo).parent.full_name else source_repo end end |
#uncommitted_changes? ⇒ Boolean
Returns whether there are local changes not committed.
187 188 189 |
# File 'lib/git-review/local.rb', line 187 def uncommitted_changes? !git_call('diff HEAD').empty? end |
#unmerged_commits?(branch_name, verbose = true) ⇒ Boolean
Returns whether there are unmerged commits on the local or remote branch.
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 |
# File 'lib/git-review/local.rb', line 195 def unmerged_commits?(branch_name, verbose=true) locations = [] locations << '' if branch_exists?(:local, branch_name) locations << 'origin/' if branch_exists?(:remote, branch_name) locations = locations.repeated_permutation(2).to_a if locations.empty? puts 'Nothing to do. All cleaned up already.' if verbose return false end # compare remote and local branch with remote and local master responses = locations.collect { |loc| git_call "cherry #{loc.first}#{target_branch} #{loc.last}#{branch_name}" } # select commits (= non empty, not just an error message and not only # duplicate commits staring with '-'). unmerged_commits = responses.reject { |response| response.empty? or response.include?('fatal: Unknown commit') or response.split("\n").reject { |x| x.index('-') == 0 }.empty? } # if the array ain't empty, we got unmerged commits if unmerged_commits.empty? false else puts "Unmerged commits on branch '#{branch_name}'." true end end |