branchbot 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0fa6c1ef194f2f10f54ac1a874d0ef1971e585f2ff9b2c8aefe958178fca3d54
4
+ data.tar.gz: fbfc9ffc56df62c39152ece3a5ca4ba28334bf857e9b42fdb4c19c65c08a6707
5
+ SHA512:
6
+ metadata.gz: 2e3b4baf23cd8108b3d096a91d946c9e0b8f175374b83103024514416ce0cb255e3e2b991ce82d6a2fb5112efe972c9891225e6294dcf7ee3f2a61f1308fc5e5
7
+ data.tar.gz: 263700bdbfa46eba2d124a21df567946bfed5209676ac1c45fa6d2612bb61782e44a76413e92ebce61446da03837e6352870f8716eea3058e8a10a41ee036ed1
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in branchbot.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,22 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ branchbot (0.1.0)
5
+ commander (~> 4.6.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ commander (4.6.0)
11
+ highline (~> 2.0.0)
12
+ highline (2.0.3)
13
+
14
+ PLATFORMS
15
+ ruby
16
+ x86_64-darwin-18
17
+
18
+ DEPENDENCIES
19
+ branchbot!
20
+
21
+ BUNDLED WITH
22
+ 2.2.19
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Room 118 Solutions Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ branchbot
2
+ ==============================
3
+
4
+ Save and restore the state of your Rails development and test databases as you work on different branches.
5
+
6
+ This allows you to make some changes to the structure of your database on a feature branch, then be able to quickly switch back to your master branch to make a hotfix or start a new feature branch with your original database structure and content.
7
+
8
+ # Installation
9
+
10
+ 1. Install the gem:
11
+
12
+ `gem install branchbot`
13
+
14
+ 2. Download the `post-checkout` file into the `.git/hooks` directory of your project.
15
+ Modify to configure any flags.
16
+
17
+ Note: Be sure to keep the `post-checkout` name to match Git's expectations.
18
+
19
+ # Support
20
+
21
+ Only PostgreSQL and MySQL databases are currently supported.
22
+
23
+ # Non-Rails Usage
24
+
25
+ While this hook is geared towards Rails and depends on Ruby, it is very easy to use it in non-Ruby/Rails projects, so long as you have Ruby installed on your system.
26
+
27
+ This script will look for a `database.yml` config file (default: `config/database.yml` in your project's root directory), and expects it to look like this:
28
+
29
+ ```yaml
30
+ development:
31
+ adapter: <adapter>
32
+ username: <database username>
33
+ password: <database password>
34
+ database: <database name>
35
+ ```
36
+
37
+ The path to this file is configurable via the `--db-config-path` option.
38
+
39
+ We currently support two adapters: `mysql2` and `postgresql`. We don't actually rely on these gems, but instead use them to determine which database's command line tools we should use (mysqldump/mysql or pg_dump/psql). Use `mysql2` if you are using a MySQL database, or `postgresql` if you are using a PostgreSQL database.
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "branchbot"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/branchbot.gemspec ADDED
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/branchbot/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "branchbot"
7
+ spec.version = Branchbot::VERSION
8
+ spec.authors = ["Jim Ryan"]
9
+ spec.email = ["jim@room118solutions.com"]
10
+ spec.license = 'MIT'
11
+ spec.summary = "Saves and restores the state of your local database as you work on different git branches."
12
+ spec.description = spec.summary
13
+ spec.homepage = "https://github.com/room118solutions/branchbot"
14
+ spec.required_ruby_version = ">= 2.4.0"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = "https://github.com/room118solutions/git-rails-database-branch-hook"
18
+ # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
24
+ end
25
+ spec.bindir = "exe"
26
+ spec.executables = ['branchbot']
27
+ spec.require_paths = ["lib"]
28
+
29
+ # Uncomment to register a new dependency of your gem
30
+ spec.add_runtime_dependency "commander", '~> 4.6.0'
31
+
32
+ # For more information and examples about making a new gem, checkout our
33
+ # guide at: https://bundler.io/guides/creating_gem.html
34
+ end
data/exe/branchbot ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "commander/import"
5
+ require "branchbot"
6
+
7
+ program :name, 'branchbot'
8
+ program :version, Branchbot::VERSION
9
+ program :description, "Saves and restores the state of your local database as you work on different git branches."
10
+
11
+ command :switch do |cmd|
12
+ cmd.syntax = "branchbot switch [options]"
13
+ cmd.option '--from FROM_REF', String, 'ref switching from'
14
+ cmd.option '--db-config-path path/to/database.yml', String, 'path to database.yml relative to project root (default: config/database.yml)'
15
+
16
+ cmd.action do |args, options|
17
+ unless options.from
18
+ say "--from required"
19
+ next
20
+ end
21
+
22
+ options.default db_config_path: 'config/database.yml'
23
+
24
+ Branchbot::BranchSwitcher.new(
25
+ database_yml_path: options.db_config_path
26
+ ).switch_from(options.from)
27
+ end
28
+ end
data/lib/branchbot.rb ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "branchbot/version"
4
+ require_relative "branchbot/adapters/abstract"
5
+ require_relative "branchbot/adapters/unsupported_database"
6
+ require_relative "branchbot/adapters/mysql"
7
+ require_relative "branchbot/adapters/postgresql"
8
+ require_relative "branchbot/branch_switcher"
9
+
10
+ require 'yaml'
11
+ require 'erb'
12
+ require 'fileutils'
13
+
14
+ module Branchbot
15
+
16
+ end
@@ -0,0 +1,74 @@
1
+ module Branchbot
2
+ module Adapters
3
+ class Abstract
4
+
5
+ def self.build(config, dump_folder)
6
+ klass = case config['adapter']
7
+ when 'postgresql'
8
+ PostgreSQL
9
+ when 'mysql2'
10
+ MySQL
11
+ else
12
+ raise UnsupportedDatabase.new(config['adapter'])
13
+ end
14
+
15
+ klass.new(config, dump_folder)
16
+ end
17
+
18
+ def initialize(config, dump_folder)
19
+ @config = config
20
+ @database_name = config['database']
21
+
22
+ @dump_folder = dump_folder
23
+ end
24
+
25
+ def dump(branch_name)
26
+ print "Saving state of database on '#{branch_name}' branch..."
27
+
28
+ if system(dump_cmd(branch_name))
29
+ print "done!\n"
30
+ true
31
+ else
32
+ print "failed!\n"
33
+ false
34
+ end
35
+ end
36
+
37
+ def dump_exists?(branch_name)
38
+ File.exists?(dump_file(branch_name))
39
+ end
40
+
41
+ def restore(branch_name)
42
+ print "Restoring #{database_name} to its previous state on this branch..."
43
+
44
+ if system(restore_cmd(branch_name))
45
+ print "done!\n"
46
+ true
47
+ else
48
+ print "failed!\n"
49
+ false
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ attr_reader :dump_folder, :database_name
56
+
57
+ def dump_cmd(branch_name)
58
+ raise NotImplementedError
59
+ end
60
+
61
+ def restore_cmd(branch_name)
62
+ raise NotImplementedError
63
+ end
64
+
65
+ def dump_file(branch_name)
66
+ # Replace any special characters that may cause file system issues
67
+ branch_name = branch_name.gsub(/[^0-9a-z.\-_]/i, '_')
68
+
69
+ "#{dump_folder}/#{database_name}-#{branch_name}"
70
+ end
71
+
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,26 @@
1
+ module Branchbot
2
+ module Adapters
3
+ class MySQL < Abstract
4
+
5
+ def initialize(*)
6
+ super
7
+
8
+ @username = @config['username']
9
+ @password = @config['password']
10
+ end
11
+
12
+ private
13
+
14
+ attr_reader :username, :password
15
+
16
+ def dump_cmd(branch_name)
17
+ %[mysqldump --add-drop-database --user=#{username} --password="#{password}" --databases #{database_name} > "#{dump_file(branch_name)}"]
18
+ end
19
+
20
+ def restore_cmd(branch_name)
21
+ %[mysql -u #{username} --password="#{password}" #{database_name} < "#{dump_file(branch_name)}"]
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,47 @@
1
+ module Branchbot
2
+ module Adapters
3
+ class PostgreSQL < Abstract
4
+
5
+ private
6
+
7
+ def dump_cmd(branch_name)
8
+ # Delete legacy SQL dump, or previous directory-format archive since folder cannot already exist
9
+ if File.exist?(dump_file(branch_name))
10
+ FileUtils.rm_r dump_file(branch_name)
11
+ end
12
+
13
+ %[pg_dump --file="#{dump_file(branch_name)}" --format=directory --jobs=#{number_of_jobs} #{database_name}]
14
+ end
15
+
16
+ def restore_cmd(branch_name)
17
+ # Drop existing connections
18
+ system(%[psql --command="SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '#{database_name}' AND pid <> pg_backend_pid();" postgres > /dev/null])
19
+
20
+ # Drop database
21
+ system(%[dropdb #{database_name}])
22
+
23
+ # Create database
24
+ system(%[createdb #{database_name}])
25
+
26
+ if File.directory?(dump_file(branch_name))
27
+ # Restore directory-format archive
28
+ %[pg_restore --dbname=#{database_name} --jobs=#{number_of_jobs} "#{dump_file(branch_name)}"]
29
+ else
30
+ # Restore legacy SQL dump
31
+ %[psql --file="#{dump_file(branch_name)}" #{database_name} > /dev/null]
32
+ end
33
+ end
34
+
35
+ def number_of_jobs
36
+ require 'etc'
37
+
38
+ if Etc.respond_to?(:nprocessors) # Available in 2.2.3+
39
+ Etc.nprocessors
40
+ else
41
+ 1
42
+ end
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,17 @@
1
+ module Branchbot
2
+ module Adapters
3
+ class UnsupportedDatabase < RuntimeError
4
+
5
+ attr_reader :adapter
6
+
7
+ def initialize(adapter)
8
+ @adapter = adapter
9
+ end
10
+
11
+ def message
12
+ "Adapter `#{adapter}` is not supported."
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,80 @@
1
+ module Branchbot
2
+ class BranchSwitcher
3
+ def initialize(database_yml_path: 'config/database.yml')
4
+ @database_yml_path = database_yml_path
5
+ end
6
+
7
+ def switch_from(prev_ref)
8
+ # Get the current (destination) branch
9
+ @destination_branch = `git rev-parse --abbrev-ref HEAD`.strip
10
+
11
+ # Since we're just given a commit ID referencing the branch head we're coming from,
12
+ # it could be at the head of multiple branches. We can assume the source isn't the same as the
13
+ # destination branch, so we can remove that immediately.
14
+ @source_branches = branches_from_refhead(prev_ref).reject{ |b| b == @destination_branch }
15
+
16
+ @project_root = %x[git rev-parse --show-toplevel].strip
17
+ @dump_folder = "#{@project_root}/.db_branch_dumps"
18
+
19
+ # Load Rails DB config and grab database name
20
+ @rails_db_config = YAML.load(ERB.new(File.read(File.join @project_root, @database_yml_path)).result)
21
+ dev_database_name = @rails_db_config['development']['database']
22
+
23
+ begin
24
+ @adapter = Adapters::Abstract.build(@rails_db_config['development'], @dump_folder)
25
+ rescue Adapters::UnsupportedDatabase => e
26
+ puts "\nERROR: #{e.message}"
27
+ exit
28
+ end
29
+
30
+ # Ensure dump directory exists
31
+ unless Dir.exists?(@dump_folder)
32
+ Dir.mkdir @dump_folder
33
+ end
34
+
35
+ # Don't do anything if the source and destination branches are the same or nonexistent
36
+ unless @source_branches.include?(@destination_branch) || @source_branches.empty? || (@source_branches | [@destination_branch]).any?{ |b| b == '' }
37
+ # Dump database for source branches
38
+ if @source_branches.all? { |branch| @adapter.dump(branch) }
39
+ # Restore dump from this branch, if it exists
40
+ if @adapter.dump_exists?(@destination_branch)
41
+ if @adapter.restore(@destination_branch)
42
+ prepare_test_database
43
+ end
44
+ else
45
+ print "No DB dump for #{dev_database_name} on the '#{@destination_branch}' branch was found!\n"
46
+ print "The state of your database has been saved for when you return to the '#{@source_branches.join('\' or \'')}' branch, but its current state has been left unchanged. You are now free to make changes to it that are specific to this branch, and they will be saved when you checkout a different branch, then restored when you checkout this one again.\n"
47
+ end
48
+ else
49
+ print "Failed to dump database. Halting.\n"
50
+ end
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def branches_from_refhead(ref)
57
+ `git show-ref --heads | grep #{ref} | awk '{print $2}'`.split("\n").map{ |b| b.sub(/^refs\/heads\//, '') }
58
+ end
59
+
60
+ def prepare_test_database
61
+ if File.exists?("#{@project_root}/Rakefile")
62
+ print "Preparing test database..."
63
+
64
+ rake_cmd = "rake db:test:prepare"
65
+
66
+ if File.exists?("#{@project_root}/bin/rake")
67
+ rake_cmd = "./bin/#{rake_cmd}"
68
+ elsif File.exists?("#{@project_root}/Gemfile")
69
+ rake_cmd = "bundle exec #{rake_cmd}"
70
+ end
71
+
72
+ system rake_cmd
73
+
74
+ print "done!\n"
75
+ else
76
+ print "No Rakefile detected, skipping test database restoration\n"
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Branchbot
4
+ VERSION = "0.1.0"
5
+ end
data/post-checkout ADDED
@@ -0,0 +1,6 @@
1
+ #!/bin/bash
2
+
3
+ if [ "$3" = "1" ]
4
+ then
5
+ branchbot switch --from=$1
6
+ fi
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: branchbot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jim Ryan
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-06-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: commander
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 4.6.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 4.6.0
27
+ description: Saves and restores the state of your local database as you work on different
28
+ git branches.
29
+ email:
30
+ - jim@room118solutions.com
31
+ executables:
32
+ - branchbot
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - Gemfile
37
+ - Gemfile.lock
38
+ - LICENSE
39
+ - README.md
40
+ - bin/console
41
+ - bin/setup
42
+ - branchbot.gemspec
43
+ - exe/branchbot
44
+ - lib/branchbot.rb
45
+ - lib/branchbot/adapters/abstract.rb
46
+ - lib/branchbot/adapters/mysql.rb
47
+ - lib/branchbot/adapters/postgresql.rb
48
+ - lib/branchbot/adapters/unsupported_database.rb
49
+ - lib/branchbot/branch_switcher.rb
50
+ - lib/branchbot/version.rb
51
+ - post-checkout
52
+ homepage: https://github.com/room118solutions/branchbot
53
+ licenses:
54
+ - MIT
55
+ metadata:
56
+ homepage_uri: https://github.com/room118solutions/branchbot
57
+ source_code_uri: https://github.com/room118solutions/git-rails-database-branch-hook
58
+ post_install_message:
59
+ rdoc_options: []
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 2.4.0
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ requirements: []
73
+ rubygems_version: 3.1.2
74
+ signing_key:
75
+ specification_version: 4
76
+ summary: Saves and restores the state of your local database as you work on different
77
+ git branches.
78
+ test_files: []