structure_conflict_resolver 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 45f2a78aeb8694f04b346175b734016414904d54
4
+ data.tar.gz: 0340ed793b083e778acf7acfb3bbeaffe98b0300
5
+ SHA512:
6
+ metadata.gz: bb69147dfe7511aaa5a1736732eaa3eaa6aa48fd167c5b726595faab70c0e77f33bf9351e0c7d0588beb961cc1ea481fa8529d754c7c50bfee1eceaebc64acaf
7
+ data.tar.gz: 8047f4ab247868123e8fdd9e11620c714b201115a6da723a6c22b8ab7f9240af57e29546d643cad2d655360058a90de4b3afe13343d2c5acc32233e876dff0a8
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ tmp/
2
+ coverage/
3
+
4
+ Gemfile.lock
5
+ *.gem
6
+
7
+ .DS_Store
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "rake"
6
+ gem "coveralls", require: false
data/README.markdown ADDED
@@ -0,0 +1,59 @@
1
+ [![Gem Version](http://img.shields.io/gem/v/structure_conflict_resolver.svg)](https://rubygems.org/gems/structure_conflict_resolver)[![Build Status](http://img.shields.io/travis/JustinAiken/structure_conflict_resolver/master.svg)](http://travis-ci.org/JustinAiken/structure_conflict_resolver)[![Coveralls branch](http://img.shields.io/coveralls/JustinAiken/structure_conflict_resolver/master.svg)](https://coveralls.io/r/JustinAiken/structure_conflict_resolver?branch=master)
2
+
3
+ # structure.sql Conflict Resolver
4
+
5
+ Ruby 💎 tool for resolving conflicts in `db/structure.sql`
6
+
7
+ Do you hate it when this happens to you?
8
+
9
+ ![Conflict Detected](doc/conflict.png)
10
+
11
+ Now you can easily resolve the most common conflicts in the versions list!
12
+
13
+ #### Before:
14
+
15
+ ```sql
16
+ ...
17
+
18
+ ('20180313190020'),
19
+ <<<<<<< HEAD
20
+ ('20180319155607');
21
+
22
+ =======
23
+ ('20180319231815');
24
+ >>>>>>> some important commit
25
+ ```
26
+
27
+ #### After:
28
+
29
+ ```sql
30
+ ...
31
+
32
+ ('20180313190020'),
33
+ ('20180319155607'),
34
+ ('20180319231815');
35
+ ```
36
+
37
+ ## Installation
38
+
39
+ ```bash
40
+ gem install structure_conflict_resolver
41
+ ```
42
+
43
+ You probably won't want to add this to your `Gemfile`, it's more of a CLI tool.
44
+
45
+ ## Usage
46
+
47
+ When you run into a merge conflict on `db/structure.sql`, give this a try:
48
+
49
+ ```bash
50
+ structure_conflict_resolver db/structure.sql
51
+ ```
52
+
53
+ It'll smartly resolve any conflicts around the version numbers at the bottom.
54
+
55
+ Any logical conflicts in the actual table descriptions... you'll have to fix yourself!
56
+
57
+ ## License
58
+
59
+ MIT
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require "rspec/core"
4
+ require "rspec/core/rake_task"
5
+ RSpec::Core::RakeTask.new(:spec) do |spec|
6
+ spec.pattern = "spec/**/*_spec.rb"
7
+ spec.rspec_opts = "--color"
8
+ end
9
+
10
+ task default: :spec
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "structure_conflict_resolver"
4
+
5
+ filename = ARGV[0]
6
+ resolver = StructureConflictResolver::Resolver.new(filename)
7
+ resolver.resolve!
data/doc/conflict.png ADDED
Binary file
@@ -0,0 +1,102 @@
1
+ begin
2
+ require "pry"
3
+ rescue LoadError
4
+ end
5
+
6
+ require "aasm"
7
+ require "rainbow"
8
+ require "structure_conflict_resolver/version"
9
+ require "structure_conflict_resolver/structure_type"
10
+ require "structure_conflict_resolver/structure_type/modern"
11
+ require "structure_conflict_resolver/structure_type/old_school"
12
+ require "structure_conflict_resolver/structure_type/unresolvable"
13
+ require "structure_conflict_resolver/merge_conflict"
14
+
15
+ module StructureConflictResolver
16
+
17
+ class Resolver
18
+
19
+ attr_accessor :filename, :conflicts, :potential_conflict
20
+
21
+ def initialize(filename)
22
+ @filename = filename
23
+ @conflicts = []
24
+ @potential_conflict = MergeConflict.new
25
+ end
26
+
27
+ def resolve!
28
+ validate_file
29
+ scan_for_conflicts
30
+ substitute_content
31
+ check_content
32
+ write_content
33
+ end
34
+
35
+ def validate_file
36
+ end_with :error, "No filename provided!" if filename.nil?
37
+ end_with :error, "db/schema.rb is not currently supported" if filename =~ /schema\.rb/
38
+ end_with :error, "#{filename} not found" unless File.exist?(filename)
39
+ end
40
+
41
+ def scan_for_conflicts
42
+ content.each_line do |line|
43
+ potential_conflict.parse! line
44
+ if potential_conflict.scanning_completed?
45
+ conflicts << potential_conflict
46
+ potential_conflict = MergeConflict.new
47
+ end
48
+ end
49
+ end
50
+
51
+ def substitute_content
52
+ conflicts
53
+ .select(&:scanning_completed?)
54
+ .each { |c| new_content.gsub! c.original_blob, c.resolved_text }
55
+ end
56
+
57
+ def check_content
58
+ end_with :error, "Unparsable conflicts!" if conflicts.any?(&:parse_error?)
59
+ end_with :error, "No resolvable conflicts!" if conflicts.all? { |c| !c.resolvable? }
60
+ end_with :error, "Nothing changed!" if content == new_content
61
+
62
+ if new_content =~ /\<{7} |\={7}|\<{7}/ || conflicts.any? { |c| !c.resolvable? }
63
+ puts Rainbow("Warning!").yellow
64
+ puts ""
65
+ puts "There are conflicts remaining. You'll have to fix those manually"
66
+ puts ""
67
+ end
68
+ end
69
+
70
+ def write_content
71
+ File.open(filename, "w") { |file| file.write new_content }
72
+ end_with :success, "✅ Version conflicts resolved!\n\n You'll probably want to \`git add #{filename}\`,\n and continue your rebase/merge.\n\n"
73
+ end
74
+
75
+ private
76
+
77
+ def content
78
+ @content ||= File.read(filename)
79
+ end
80
+
81
+ def new_content
82
+ @new_content ||= content.dup
83
+ end
84
+
85
+ def end_with(exit_type, msg)
86
+ puts ""
87
+ if exit_type == :error
88
+ puts Rainbow("Error!").red
89
+ puts ""
90
+ puts msg
91
+ puts ""
92
+ exit 1
93
+ else
94
+ puts Rainbow("Success!").green
95
+ puts ""
96
+ puts msg
97
+ puts ""
98
+ end
99
+ exit 0
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StructureConflictResolver
4
+ class MergeConflict
5
+ include AASM
6
+
7
+ CONFLICT_A = /^\<{7} (.*)$/
8
+ DIVIDER = /^\={7}$/
9
+ CONFLICT_B = /^\>{7} (.+)$/
10
+
11
+ attr_accessor :current_line, :original_blob, :head_branch_blob, :our_commit_blob
12
+
13
+ def initialize
14
+ @original_blob = String.new
15
+ @head_branch_blob = String.new
16
+ @our_commit_blob = String.new
17
+ end
18
+
19
+ aasm do
20
+ state :not_detected, initial: true
21
+ state :in_head_blob
22
+ state :in_our_blob
23
+ state :scanning_completed
24
+
25
+ event :enter_head_blob, if: :matches_conflict_start? do
26
+ transitions from: :not_detected, to: :in_head_blob
27
+ end
28
+
29
+ event :enter_our_blob, if: :at_divider? do
30
+ transitions from: :in_head_blob, to: :in_our_blob
31
+ end
32
+
33
+ event :complete_scanning, if: :matches_conflict_end? do
34
+ transitions from: :in_our_blob, to: :scanning_completed
35
+ end
36
+ end
37
+
38
+ def parse!(line)
39
+ @current_line = line
40
+ enter_head_blob if may_enter_head_blob?
41
+ enter_our_blob if may_enter_our_blob?
42
+ complete_scanning if may_complete_scanning?
43
+
44
+ store_lines!
45
+ end
46
+
47
+ def parse_error?
48
+ !not_detected? && !scanning_completed?
49
+ end
50
+
51
+ def resolvable?
52
+ !resolved_text.nil?
53
+ end
54
+
55
+ def resolved_text
56
+ @resolved_text ||= StructureType.from(head_branch_blob + our_commit_blob).resolved
57
+ rescue
58
+ end
59
+
60
+ private
61
+
62
+ def store_lines!
63
+ original_blob << current_line if in_head_blob? || in_our_blob? || git_noise?
64
+
65
+ unless git_noise?
66
+ head_branch_blob << current_line if in_head_blob?
67
+ our_commit_blob << current_line if in_our_blob?
68
+ end
69
+ end
70
+
71
+ def git_noise?
72
+ matches_conflict_start? || at_divider? || matches_conflict_end?
73
+ end
74
+
75
+ def matches_conflict_start?
76
+ current_line =~ CONFLICT_A
77
+ end
78
+
79
+ def matches_conflict_end?
80
+ current_line =~ CONFLICT_B
81
+ end
82
+
83
+ def at_divider?
84
+ current_line =~ DIVIDER
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StructureConflictResolver
4
+ module StructureType
5
+
6
+ def self.from(text_blob)
7
+ klass = case text_blob
8
+ when /INSERT INTO schema_migrations/ then OldSchool
9
+ when /\(\'\d{5,14}\'\)[,; ]?/ then Modern
10
+ else Unresolvable
11
+ end
12
+
13
+ klass.new(text_blob)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StructureConflictResolver
4
+ module StructureType
5
+ class Modern
6
+
7
+ attr_accessor :original_text
8
+
9
+ def initialize(original_text)
10
+ @original_text = original_text
11
+ end
12
+
13
+ def resolved
14
+ original_text
15
+ .gsub(";", ",")
16
+ .split("\n")
17
+ .reject(&:empty?)
18
+ .sort
19
+ .uniq
20
+ .tap { |strings| strings.last.gsub! ",", ";\n" }
21
+ .join("\n")
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StructureConflictResolver
4
+ module StructureType
5
+ class OldSchool
6
+
7
+ attr_accessor :original_text
8
+
9
+ def initialize(original_text)
10
+ @original_text = original_text
11
+ end
12
+
13
+ def resolved
14
+ original_text
15
+ .split("\n")
16
+ .reject { |line| line.empty? }
17
+ .sort
18
+ .uniq
19
+ .join("\n\n") + "\n"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StructureConflictResolver
4
+ module StructureType
5
+ class Unresolvable
6
+
7
+ attr_accessor :original_text
8
+
9
+ def initialize(original_text)
10
+ @original_text = original_text
11
+ end
12
+
13
+ def resolved
14
+ raise StandardError
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module StructureConflictResolver
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+
4
+ describe StructureConflictResolver::MergeConflict do
5
+ subject(:conflict) { described_class.new }
6
+
7
+ describe ".aasm" do
8
+ it "moves through the states" do
9
+ conflict.parse! "OK LINE"
10
+ expect(conflict).to be_not_detected
11
+ conflict.parse! "OK LINE"
12
+ expect(conflict).to be_not_detected
13
+
14
+ conflict.parse! "<<<<<<< HEAD"
15
+ expect(conflict).to be_in_head_blob
16
+ conflict.parse! "foo line"
17
+ expect(conflict).to be_in_head_blob
18
+
19
+ conflict.parse! "======="
20
+ expect(conflict).to be_in_our_blob
21
+ conflict.parse! "another line"
22
+ expect(conflict).to be_in_our_blob
23
+
24
+ conflict.parse! ">>>>>>> some commit I just broke"
25
+ expect(conflict).to be_scanning_completed
26
+ end
27
+ end
28
+
29
+ describe "#resolvable? / # resolved_text" do
30
+ before { lines.each_line { |line| conflict.parse! line } }
31
+
32
+ context "when it's a tangled mess" do
33
+ let(:lines) do
34
+ <<~TEXT
35
+ <<<<<<< HEAD
36
+ CREATE TABLE `foos` (
37
+ `id` int(11) NOT NULL AUTO_INCREMENT
38
+ =======
39
+ `id` int(11) DEFAULT NULL AUTO_INCREMENT
40
+ >>>>>>> my branch
41
+ TEXT
42
+ end
43
+
44
+ it { should_not be_resolvable }
45
+ end
46
+
47
+ context "when it's doable with the modern style" do
48
+ let(:lines) do
49
+ <<~TEXT
50
+ <<<<<<< HEAD
51
+ ('20140521142114'),
52
+ ('20140521195156');
53
+ =======
54
+ ('20140521195156'),
55
+ ('20140521195156');
56
+ >>>>>>> my branch
57
+ TEXT
58
+ end
59
+
60
+ it { should be_resolvable }
61
+
62
+ it "is resolved correctly" do
63
+ expect(conflict.resolved_text).to eq(
64
+ <<~TEXT
65
+ ('20140521142114'),
66
+ ('20140521195156');
67
+ TEXT
68
+ )
69
+ end
70
+ end
71
+
72
+ context "when it's doable with the old school style" do
73
+ let(:lines) do
74
+ <<~TEXT
75
+ <<<<<<< HEAD
76
+ INSERT INTO schema_migrations (version) VALUES ('20170101120000');
77
+
78
+ INSERT INTO schema_migrations (version) VALUES ('20170101150000');
79
+ =======
80
+ INSERT INTO schema_migrations (version) VALUES ('20170101110000');
81
+
82
+ INSERT INTO schema_migrations (version) VALUES ('20170101130000');
83
+
84
+ INSERT INTO schema_migrations (version) VALUES ('20170101150000');
85
+ >>>>>>> my branch
86
+ TEXT
87
+ end
88
+
89
+ it { should be_resolvable }
90
+
91
+ it "is resolved correctly" do
92
+ expect(conflict.resolved_text).to eq(
93
+ <<~TEXT
94
+ INSERT INTO schema_migrations (version) VALUES ('20170101110000');
95
+
96
+ INSERT INTO schema_migrations (version) VALUES ('20170101120000');
97
+
98
+ INSERT INTO schema_migrations (version) VALUES ('20170101130000');
99
+
100
+ INSERT INTO schema_migrations (version) VALUES ('20170101150000');
101
+ TEXT
102
+ )
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,11 @@
1
+ unless ENV["NO_COVERALLS"]
2
+ require "coveralls"
3
+ Coveralls.wear!
4
+ end
5
+
6
+ require "structure_conflict_resolver"
7
+
8
+ RSpec.configure do |config|
9
+ config.filter_run :focus
10
+ config.run_all_when_everything_filtered = true
11
+ end
@@ -0,0 +1,29 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "structure_conflict_resolver/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.platform = Gem::Platform::RUBY
7
+ s.name = "structure_conflict_resolver"
8
+ s.version = StructureConflictResolver::VERSION
9
+ s.authors = ["Justin Aiken"]
10
+ s.email = ["60tonangel@gmail.com"]
11
+ s.license = "MIT"
12
+ s.homepage = "https://github.com/JustinAiken/structure_conflict_resolver"
13
+ s.summary = %q{Automate tedious db/structure.sql conflict resolution}
14
+ s.description = %q{Automate tedious db/structure.sql conflict resolution}
15
+
16
+ s.rubyforge_project = "structure_conflict_resolver"
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {spec}/*`.split("\n")
20
+ s.require_paths = ["lib"]
21
+ s.bindir = "bin"
22
+ s.executables = s.files.grep(%r{^bin/}).map { |f| File.basename(f) }
23
+
24
+ s.add_dependency "rainbow", "~> 3.0"
25
+ s.add_dependency "aasm", "~> 4.12"
26
+
27
+ s.add_development_dependency "rspec", "~> 3.5"
28
+ s.add_development_dependency "pry"
29
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: structure_conflict_resolver
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Justin Aiken
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-04-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rainbow
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: aasm
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.12'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.12'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.5'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Automate tedious db/structure.sql conflict resolution
70
+ email:
71
+ - 60tonangel@gmail.com
72
+ executables:
73
+ - structure_conflict_resolver
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - ".travis.yml"
80
+ - Gemfile
81
+ - README.markdown
82
+ - Rakefile
83
+ - bin/structure_conflict_resolver
84
+ - doc/conflict.png
85
+ - lib/structure_conflict_resolver.rb
86
+ - lib/structure_conflict_resolver/merge_conflict.rb
87
+ - lib/structure_conflict_resolver/structure_type.rb
88
+ - lib/structure_conflict_resolver/structure_type/modern.rb
89
+ - lib/structure_conflict_resolver/structure_type/old_school.rb
90
+ - lib/structure_conflict_resolver/structure_type/unresolvable.rb
91
+ - lib/structure_conflict_resolver/version.rb
92
+ - spec/lib/structure_conflict_resolver/merge_conflict_spec.rb
93
+ - spec/spec_helper.rb
94
+ - structure_conflict_resolver.gemspec
95
+ homepage: https://github.com/JustinAiken/structure_conflict_resolver
96
+ licenses:
97
+ - MIT
98
+ metadata: {}
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubyforge_project: structure_conflict_resolver
115
+ rubygems_version: 2.6.14
116
+ signing_key:
117
+ specification_version: 4
118
+ summary: Automate tedious db/structure.sql conflict resolution
119
+ test_files: []