structure_conflict_resolver 0.0.1

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
+ 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: []