Class: Gitscape::Base
- Inherits:
-
Object
- Object
- Gitscape::Base
- Defined in:
- lib/gitscape/base.rb
Class Method Summary collapse
-
.commit_exists?(commit_id) ⇒ Boolean
Returns true if the supplied Git commit hash or reference exists.
Instance Method Summary collapse
- #bugfix_finish(branch_name = nil, options = {:env_depth=>:staging, :push=>true, :update_env=>false}) ⇒ Object
- #bugfix_start(new_branch = nil, options = {:push=>false}) ⇒ Object
- #current_branch_name ⇒ Object
- #current_release_branch_name ⇒ Object
- #current_release_branch_number ⇒ Object
- #feature_finish(branch_name = nil, options = {:env_depth=>:staging, :push=>true, :update_env=>false}) ⇒ Object
- #feature_start(new_branch = nil, options = {:push=>false}) ⇒ Object
- #finish_usage_string(branch_type) ⇒ Object
- #generic_branch_finish(branch_type, source_name, options) ⇒ Object
- #generic_branch_start(branch_type, from_branch, new_branch, options) ⇒ Object
- #git_has_conflicts(puts_conflicts = true) ⇒ Object
-
#git_version ⇒ Object
Get the system’s current git version.
-
#git_version_at_least(min_version) ⇒ Object
Check if the system’s git version is at least as recent as the version specified.
- #git_working_copy_is_clean(puts_changes = true) ⇒ Object
- #hotfix_finish(branch_name = nil, options = {:env_depth=>:staging, :push=>true, :update_env=>false}) ⇒ Object
- #hotfix_start(new_branch = nil, options = {:push=>false}) ⇒ Object
-
#initialize ⇒ Base
constructor
A new instance of Base.
-
#live_iteration ⇒ Object
Assume the highest branch already merged into live of the form release/i+ is the live branch.
- #release_finish(options = {:push=>true, :update_env=>true}) ⇒ Object
- #release_start(options = {:push=>true, :update_env=>true}) ⇒ Object
- #split_version(v) ⇒ Object
- #tag_cleanup(options) ⇒ Object
Constructor Details
#initialize ⇒ Base
Returns a new instance of Base.
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/gitscape/base.rb', line 6 def initialize # Always add a merge commit at the end of a merge @merge_options = "--no-ff" # Setup additional merge options based on the version of Git we have if git_version_at_least "1.7.4.0" @merge_options += " -s recursive -Xignore-space-change" else warn "Ignoring whitespace changes in merges is only available on Git 1.7.4+" end @env_branch_by_dev_branch = Hash.new do |h, k| case k when "master" "staging" when /release\/i\d+/ "qa" when "live" "live" end end end |
Class Method Details
.commit_exists?(commit_id) ⇒ Boolean
Returns true if the supplied Git commit hash or reference exists
376 377 378 379 380 381 382 383 |
# File 'lib/gitscape/base.rb', line 376 def self.commit_exists?(commit_id) `git rev-parse #{commit_id}` if $? == 0 true else raise "Invalid commit/ref ID: #{commit_id}" end end |
Instance Method Details
#bugfix_finish(branch_name = nil, options = {:env_depth=>:staging, :push=>true, :update_env=>false}) ⇒ Object
130 131 132 |
# File 'lib/gitscape/base.rb', line 130 def bugfix_finish branch_name=nil, ={:env_depth=>:staging, :push=>true, :update_env=>false} generic_branch_finish 'bugfix', branch_name, end |
#bugfix_start(new_branch = nil, options = {:push=>false}) ⇒ Object
113 114 115 116 117 118 119 120 |
# File 'lib/gitscape/base.rb', line 113 def bugfix_start new_branch=nil, ={:push=>false} name = current_release_branch_name if name.nil? puts 'There is not a current release branch. You cannot use this command.' else generic_branch_start 'bugfix', name, new_branch, end end |
#current_branch_name ⇒ Object
57 58 59 60 |
# File 'lib/gitscape/base.rb', line 57 def current_branch_name toRet = `git branch`.scan(/\* (.*)$/).flatten[0] toRet end |
#current_release_branch_name ⇒ Object
70 71 72 73 74 75 76 77 |
# File 'lib/gitscape/base.rb', line 70 def current_release_branch_name release_number = current_release_branch_number if !release_number.nil? "release/i#{current_release_branch_number}" else nil end end |
#current_release_branch_number ⇒ Object
62 63 64 65 66 67 68 |
# File 'lib/gitscape/base.rb', line 62 def current_release_branch_number unmerged_into_live_branch_names = `git branch -a --no-merged origin/live`.split("\n") release_branch_regex = /release\/i(\d+)$/ candidates = unmerged_into_live_branch_names.select{ |b| release_branch_regex.match b}.map{|b| b.scan(release_branch_regex).flatten[0].to_i}.sort candidates.last end |
#feature_finish(branch_name = nil, options = {:env_depth=>:staging, :push=>true, :update_env=>false}) ⇒ Object
134 135 136 |
# File 'lib/gitscape/base.rb', line 134 def feature_finish branch_name=nil, ={:env_depth=>:staging, :push=>true, :update_env=>false} generic_branch_finish 'feature', branch_name, end |
#feature_start(new_branch = nil, options = {:push=>false}) ⇒ Object
122 123 124 |
# File 'lib/gitscape/base.rb', line 122 def feature_start new_branch=nil, ={:push=>false} generic_branch_start 'feature', 'master', new_branch, end |
#finish_usage_string(branch_type) ⇒ Object
410 411 412 413 414 |
# File 'lib/gitscape/base.rb', line 410 def finish_usage_string(branch_type) "expected usage: #{branch_type}_finish [<#{branch_type}_branch>] #{branch_type}_branch: the name of the #{branch_type} branch to finish. if omitted, you must currently be on a #{branch_type} branch" end |
#generic_branch_finish(branch_type, source_name, options) ⇒ Object
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'lib/gitscape/base.rb', line 138 def generic_branch_finish branch_type, source_name, # option defaults [:env_depth] = :staging if [:env_depth].nil? [:push] = true if [:push].nil? [:update_env] = false if [:update_env].nil? if ([:env_depth] == :qa) && branch_type == 'feature' puts "*** --qa may not be used with feature branches" exit 1 end if ([:env_depth] == :live) && branch_type != 'hotfix' puts "*** --live may only be used with hotfix branches" exit 1 end # Check that the working copy is clean exit 1 unless git_working_copy_is_clean source_branch = "#{branch_type}/#{source_branch}" previous_branch = current_branch_name if previous_branch.to_s.start_with? "#{branch_type}/" source_branch = previous_branch end if source_branch.to_s.empty? puts "!!! Not currently on a #{branch_type} branch, and no branch name was provided as an argument !!!" puts finish_usage_string(branch_type) exit 1 end # Collect the set of branches we'd like to merge the hotfix into merge_branches = ["master"] if %w{bugfix hotfix}.include?(branch_type) if current_release_branch_name.nil? puts "!!! There is no current release branch: the command will bypass the release and qa branches" else merge_branches << current_release_branch_name if [:qa, :live].include?([:env_depth]) end end if %w{hotfix}.include?(branch_type) merge_branches << "live" if [:env_depth] == :live end # Merge the source branch into merge_branches puts "=== Merging #{branch_type} into branches #{merge_branches} ===" for branch in merge_branches # Calculate merge_options = @merge_options += " --log" if branch == "master" # Attempt merge puts `git checkout #{branch}` puts `git pull` puts `git merge #{} #{source_branch}` # Bail on failures exit 1 if !$?.success? raise "Merge failure(s) on #{branch}.\nResolve conflicts, and run the script again." if git_has_conflicts puts `git push origin #{branch}` if [:push] puts `git push origin #{branch}:#{@env_branch_by_dev_branch[branch]}` if [:update_env] # If we just merged the live branch, tag this revision, and push that tag to origin if branch == "live" puts `git tag live/i#{live_iteration}/#{source_branch}` puts `git push --tags` end end # Checkout previous branch for user convenience `git checkout #{previous_branch}` end |
#generic_branch_start(branch_type, from_branch, new_branch, options) ⇒ Object
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/gitscape/base.rb', line 88 def generic_branch_start branch_type, from_branch, new_branch, # option defaults [:push] = false if [:push].nil? # Check that the working copy is clean exit 1 unless git_working_copy_is_clean if new_branch.to_s.length == 0 raise "*** Improper Usage ***\nExpected Usage: #{branch_type}_start <#{branch_type}_name> [--[no-]push]" end puts `git checkout #{from_branch}` puts `git pull origin #{from_branch}` new_branch = "#{branch_type}/#{new_branch}" puts "=== Creating #{branch_type} branch '#{new_branch}' ===" puts `git checkout -b #{new_branch}` puts `git push origin #{new_branch}` if [:push] end |
#git_has_conflicts(puts_conflicts = true) ⇒ Object
79 80 81 82 83 84 85 86 |
# File 'lib/gitscape/base.rb', line 79 def git_has_conflicts puts_conflicts=true conflicts_status = `git status --porcelain` has_conflicts = conflicts_status.scan(/[AUD]{2}/).count > 0 puts conflicts_status if has_conflicts && puts_conflicts has_conflicts end |
#git_version ⇒ Object
Get the system’s current git version
386 387 388 |
# File 'lib/gitscape/base.rb', line 386 def git_version @git_version ||= `git --version`.match(/\d+(?:\.\d+)+/).to_s end |
#git_version_at_least(min_version) ⇒ Object
Check if the system’s git version is at least as recent as the version specified
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 |
# File 'lib/gitscape/base.rb', line 391 def git_version_at_least(min_version) def split_version(v) v.split(".").map { |x| x.to_i } end local_version = split_version(git_version) min_version = split_version(min_version) raise "Git version string must have 4 parts" if min_version.size != 4 4.times do |i| if local_version[i].nil? return false end next if local_version[i] == min_version[i] return local_version[i] > min_version[i] end true # If you get all the way here, all 4 positions match precisely end |
#git_working_copy_is_clean(puts_changes = true) ⇒ Object
31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/gitscape/base.rb', line 31 def git_working_copy_is_clean puts_changes=true # Check if the working copy is clean, if not, exit changes = `git status -uno --ignore-submodules=all --porcelain` working_copy_clean = changes.length == 0 if !working_copy_clean && puts_changes puts "*** Your working copy is not clean, either commit, stash, or reset your changes then try again. ***" puts changes end working_copy_clean end |
#hotfix_finish(branch_name = nil, options = {:env_depth=>:staging, :push=>true, :update_env=>false}) ⇒ Object
126 127 128 |
# File 'lib/gitscape/base.rb', line 126 def hotfix_finish branch_name=nil, ={:env_depth=>:staging, :push=>true, :update_env=>false} generic_branch_finish 'hotfix', branch_name, end |
#hotfix_start(new_branch = nil, options = {:push=>false}) ⇒ Object
109 110 111 |
# File 'lib/gitscape/base.rb', line 109 def hotfix_start new_branch=nil, ={:push=>false} generic_branch_start 'hotfix', 'live', new_branch, end |
#live_iteration ⇒ Object
Assume the highest branch already merged into live of the form release/i+ is the live branch
47 48 49 50 51 52 53 54 55 |
# File 'lib/gitscape/base.rb', line 47 def live_iteration live_iteration_tag_regex = /^live\/i(\d+)/ toRet = `git tag`.split("\n").select { |tag| live_iteration_tag_regex.match tag }.map { |tag| tag.scan(live_iteration_tag_regex).flatten[0].to_i }.sort.last # A bit of a chicken-and-egg problem. You might not have any tags for live, so look for something else... if toRet.nil? toRet = `git branch -a --merged origin/live`.split("\n").select{|b| /release\/i(\d+)$/.match b}.map{|b| b.scan(/release\/i(\d+)$/).flatten[0].to_i}.sort.last end toRet end |
#release_finish(options = {:push=>true, :update_env=>true}) ⇒ Object
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 |
# File 'lib/gitscape/base.rb', line 255 def release_finish ={:push=>true, :update_env=>true} # Handle default options [:push] = true if [:push].nil? [:update_env] = true if [:update_env].nil? # Check if the working copy is clean, if not, exit exit 1 unless git_working_copy_is_clean # Do a git fetch to ensure everything all refs are sync with origin puts `git fetch` # Get the right release_branch_name to merge current_version_number = live_iteration new_version_number = current_version_number + 1 release_branch = "release/i#{new_version_number}" # Fetch in order to have the latest branch revisions # Get branch information for checks branch_keys = ["name", "revision", "message"] branch_values = `git branch -av`.scan(/^[ \*]*([^ \*]+) +([^ ]+) +(.*)$/) branches = branch_values.map {|components| Hash[ branch_keys.zip components ] } branch_revisions = Hash[ branches.map {|branch| [branch["name"], branch["revision"]] } ] # Check if the required branches in sync required_synced_branches = [[release_branch, "remotes/origin/qa"]] required_synced_branches.each do |branch_pair| if branch_revisions[ branch_pair[0] ] != branch_revisions[ branch_pair[0] ] puts "*** ERROR: The #{branch_pair[0]} branch is not the same as the #{branch_pair[1]} branch. \tPlease resolve this and try again." exit 3 end end # Checkout and pull release_branch puts `git checkout #{release_branch}` puts `git pull origin #{release_branch}` # Checkout and pull live puts `git checkout live` puts `git pull origin live` = "--no-ff -s recursive -Xignore-space-change" # Merge the release branch into live puts `git merge #{} #{release_branch}` # Error and conflict checking if !$?.success? then exit 4 end if git_has_conflicts then puts "Merge conflicts when pulling #{release_branch} into live" puts "Please report a problem if you see this message :)" exit 2 end # Ensure there is zero diff between what was tested on origin/qa and the new live critical_diff = `git diff live origin/qa` if critical_diff.length > 0 puts "\n!!! This live merge has code that was not on the qa branch !!!\nDiff:" puts critical_diff puts "!!! Run the command 'git reset --hard' to undo the merge, and raise this error with QA and others involved to determine next step !!!" exit 3 end # Record the revision of live used for the release tag live_release_revision = `git log -n1 --oneline`.scan(/(^[^ ]+) .*$/).flatten[0] # Merge the release branch into master puts `git checkout master` puts `git pull origin master` puts `git merge #{} #{release_branch}` # Error and conflict checking if !$?.success? then exit 4 end if git_has_conflicts then puts "Merge conflicts when pulling #{release_branch} into master" puts "Please report a problem if you see this message :)" exit 2 end # Tag the state of live for both release and rollback puts `git tag rollback-to/i#{current_version_number} live~` if !$?.success? then puts "=== WARNING: Failed to create rollback-to/i#{current_version_number} tag" end `git tag live/i#{new_version_number}/release #{live_release_revision}` if !$?.success? then puts "=== WARNING: Failed to create live/i#{new_version_number}/release" puts `git tag -d rollback-to/i#{current_version_number}` exit 4 end if [:push] && [:update_env] puts `git push origin live --tags` puts `git push origin master` end end |
#release_start(options = {:push=>true, :update_env=>true}) ⇒ Object
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/gitscape/base.rb', line 215 def release_start ={:push=>true, :update_env=>true} # Handle default options [:push] = true if [:push].nil? [:update_env] = true if [:update_env].nil? # Switch to master branch puts `git checkout master` puts `git pull origin master` # Check that the working copy is clean exit 1 unless git_working_copy_is_clean new_version_number = live_iteration + 1 release_branch_name = "release/i#{new_version_number}" # Cut the branch puts `git checkout -b "#{release_branch_name}" master` exit 1 unless $?.exitstatus == 0 # Bump the version number `echo "i#{new_version_number}" > ./version` exit 1 unless $?.exitstatus == 0 # Commit the bump puts `git commit -a -m "Begin i#{new_version_number} release candidate"` exit 1 unless $?.exitstatus == 0 # Push to origin if [:push] puts `git push origin -u "#{release_branch_name}"` exit 1 unless $?.exitstatus == 0 end # Update qa to the new commit if [:update_env] puts `git push origin "#{release_branch_name}:qa"` exit 1 unless $?.exitstatus == 0 end end |
#split_version(v) ⇒ Object
392 393 394 |
# File 'lib/gitscape/base.rb', line 392 def split_version(v) v.split(".").map { |x| x.to_i } end |
#tag_cleanup(options) ⇒ Object
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 |
# File 'lib/gitscape/base.rb', line 355 def tag_cleanup # Handle default options [:push] = false if [:push].nil? # Do a fetch in order to ensure we have all the latest tags `git fetch` # Select which tags to keep. # We currently keep tags which fulfill any of the following # 1. starts with 'service/' # 2. starts with 'rollback-to/' or 'live/', and has an iteration number >= the live_iteration number - 3 = `git tag`.split "\n" = .select { |tag| !(!/^service\//.match(tag).nil? || /^(?:live|rollback-to)\/i(\d+)/.match(tag).to_a[1].to_i >= live_iteration - 3) } puts "Deleting the following tags.\nThese changes #{[:push] ? "will" : "will not"} be pushed to origin.\n" .each { |tag| puts `git tag -d #{tag}` } .each { |tag| puts `git push origin :refs/tags/#{tag}` } if [:push] end |