kdiff3 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +37 -0
- data/.rspec +2 -0
- data/.travis.yml +16 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +71 -0
- data/LICENSE +340 -0
- data/README.md +52 -0
- data/Rakefile +1 -0
- data/bin/kdiff3 +0 -0
- data/kdiff3.gemspec +34 -0
- data/lib/kdiff3.rb +173 -0
- data/lib/kdiff3/version.rb +3 -0
- data/spec/kdiff3_spec.rb +121 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/support/a +7 -0
- data/spec/support/b +9 -0
- data/spec/support/base +10 -0
- data/spec/support/expected +6 -0
- metadata +608 -0
data/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
KDiff3
|
2
|
+
========
|
3
|
+
|
4
|
+
[![Gem Version](http://img.shields.io/gem/v/KDiff3.svg?style=flat-square)](http://badge.fury.io/rb/KDiff3)
|
5
|
+
[![Build Status](http://img.shields.io/travis/NullVoxPopuli/kdiff3-rb.svg?style=flat-square)](https://travis-ci.org/NullVoxPopuli/kdiff3-rb)
|
6
|
+
[![Code Climate](http://img.shields.io/codeclimate/github/NullVoxPopuli/kdiff3-rb.svg?style=flat-square)](https://codeclimate.com/github/NullVoxPopuli/kdiff3-rb)
|
7
|
+
[![Test Coverage](http://img.shields.io/codeclimate/coverage/github/NullVoxPopuli/kdiff3-rb.svg?style=flat-square)](https://codeclimate.com/github/NullVoxPopuli/kdiff3-rb)
|
8
|
+
[![Dependency Status](http://img.shields.io/gemnasium/NullVoxPopuli/kdiff3-rb.svg?style=flat-square)](https://gemnasium.com/NullVoxPopuli/kdiff3-rb)
|
9
|
+
[![security](https://hakiri.io/github/NullVoxPopuli/kdiff3-rb/master.svg)](https://hakiri.io/github/NullVoxPopuli/kdiff3-rb/master)
|
10
|
+
|
11
|
+
Provides a simple way to utilize the power of [kdiff3 by Joachim Eibl](http://kdiff3.sourceforge.net/) in ruby.
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
gem install KDiff3
|
16
|
+
|
17
|
+
or
|
18
|
+
|
19
|
+
gem 'KDiff3' # in your Gemfile
|
20
|
+
|
21
|
+
## Examples
|
22
|
+
|
23
|
+
Merging inline HTML
|
24
|
+
|
25
|
+
result = KDiff3.merge(
|
26
|
+
base: "<p>1,2,<p>1,<p>2,</p></p>3</p>",
|
27
|
+
yours: "<p>1,2,<p>1,</p>3</p>",
|
28
|
+
theirs: "<p>1,2,<p><p>2,</p></p>3</p>",
|
29
|
+
html: true
|
30
|
+
)
|
31
|
+
|
32
|
+
# => "<p>1,2,<p></p>3</p>"
|
33
|
+
|
34
|
+
Merging files
|
35
|
+
|
36
|
+
result = KDiff3.merge(
|
37
|
+
base: "path/to/base",
|
38
|
+
yours: "path/to/yours",
|
39
|
+
theirs: "path/to/theirs"
|
40
|
+
)
|
41
|
+
|
42
|
+
# => (whatever merged output is from the files)
|
43
|
+
|
44
|
+
|
45
|
+
## Development
|
46
|
+
|
47
|
+
KDiff3 exists in ext/kdiff3 using a git subtree
|
48
|
+
- KDiff3 repo here: https://github.com/NullVoxPopuli/kdiff3
|
49
|
+
- More info on subtrees here: https://medium.com/@v/git-subtrees-a-tutorial-6ff568381844
|
50
|
+
|
51
|
+
## Contributing
|
52
|
+
- Fork, Test, Pull Request :-)
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/bin/kdiff3
ADDED
Binary file
|
data/kdiff3.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require "kdiff3/version"
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "kdiff3"
|
9
|
+
s.version = KDiff3::VERSION
|
10
|
+
s.platform = Gem::Platform::RUBY
|
11
|
+
s.license = "GNU GPL v2"
|
12
|
+
s.authors = ["L. Preston Sego III"]
|
13
|
+
s.email = "LPSego3+dev@gmail.com"
|
14
|
+
s.homepage = "https://github.com/NullVoxPopuli/kdiff3-rb"
|
15
|
+
s.summary = "kdiff3-#{KDiff3::VERSION}"
|
16
|
+
s.description = "Ruby wrapper for the kdiff3 mergetool"
|
17
|
+
|
18
|
+
|
19
|
+
s.files = `git ls-files`.split($/)
|
20
|
+
s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
21
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
22
|
+
s.require_paths = ["lib"]
|
23
|
+
|
24
|
+
s.extensions = ['ext/kdiff3/extconf.rb']
|
25
|
+
|
26
|
+
s.add_dependency 'activesupport', '>= 3.2'
|
27
|
+
|
28
|
+
s.add_development_dependency "bundler", '>= 1.6.0'
|
29
|
+
s.add_development_dependency "awesome_print", '>= 1.2'
|
30
|
+
s.add_development_dependency "rspec", '>= 3.1.0'
|
31
|
+
s.add_development_dependency "pry-byebug", '>= 2.0.0'
|
32
|
+
s.add_development_dependency "codeclimate-test-reporter", '>= 0.4.3'
|
33
|
+
|
34
|
+
end
|
data/lib/kdiff3.rb
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
# string.present?
|
2
|
+
require 'active_support/core_ext/string'
|
3
|
+
|
4
|
+
module KDiff3
|
5
|
+
|
6
|
+
# list of created Tempfiles
|
7
|
+
TEMPFILES = []
|
8
|
+
# so we don't accidentally mess up formatting when we remove these later,
|
9
|
+
# we need to have a weird, non-stand sequence of characters to search
|
10
|
+
# and replace
|
11
|
+
NEWLINE = "\n\-"
|
12
|
+
|
13
|
+
|
14
|
+
# performs a 3 way merge between base, a, and b
|
15
|
+
# a 2 way merge will be performed if either yours
|
16
|
+
# or theirs are left out
|
17
|
+
#
|
18
|
+
# @param [String] base string or file path for common ancestor
|
19
|
+
# @param [String] yours string or file path for your changes
|
20
|
+
# @param [String] theirs string or file path for their changes
|
21
|
+
# @param [Boolean] html whether or not use an HTML diffing technique
|
22
|
+
# @return [String] result of merge
|
23
|
+
def self.merge(base: nil, yours: nil, theirs: nil, html: false)
|
24
|
+
raise ArgumentError.new('base is required') unless base.present?
|
25
|
+
raise ArgumentError.new('yours and/or theirs required') unless yours || theirs
|
26
|
+
|
27
|
+
# since HTML is often compressed to conserve transfer space, and therefore
|
28
|
+
# has few lines, we need to split up the HTML in to a multi-lined document
|
29
|
+
if html
|
30
|
+
base = add_new_lines(base)
|
31
|
+
yours = add_new_lines(yours)
|
32
|
+
theirs = add_new_lines(theirs)
|
33
|
+
end
|
34
|
+
|
35
|
+
base_path = tempfile(text: base, name: 'base')
|
36
|
+
your_path = tempfile(text: yours, name: 'yours')
|
37
|
+
their_path = tempfile(text: theirs, name: 'theirs')
|
38
|
+
output_path = tempfile(name: 'output')
|
39
|
+
|
40
|
+
# we don't need these open for anything
|
41
|
+
close_tempfiles
|
42
|
+
|
43
|
+
# the heavy lifting, courtesy of kdiff3
|
44
|
+
exit_code = run("#{base_path} #{your_path} #{their_path} -m --auto --fail -o #{output_path}")
|
45
|
+
conflicts_exist = exit_code == 1
|
46
|
+
|
47
|
+
result = IO.read(output_path) unless conflicts_exist
|
48
|
+
|
49
|
+
# clean up
|
50
|
+
delete_tempfiles
|
51
|
+
|
52
|
+
raise RuntimeError.new("Conflicts exist and could not be resolved") if conflicts_exist
|
53
|
+
|
54
|
+
if html
|
55
|
+
# remove the NEWLINES
|
56
|
+
result.gsub!(NEWLINE, "")
|
57
|
+
end
|
58
|
+
|
59
|
+
result
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# @param [String] name of the file
|
65
|
+
# @param [String] text content of file or path
|
66
|
+
# @return [String] path of the Tempfile or Pre-existing file
|
67
|
+
def self.tempfile(text: nil, name: nil)
|
68
|
+
result = ""
|
69
|
+
|
70
|
+
# if the file already exists,
|
71
|
+
# don't add it to the tempfile list,
|
72
|
+
# as we don't want it to be deleted
|
73
|
+
if text && is_file_path?(text)
|
74
|
+
result = text
|
75
|
+
else
|
76
|
+
t = Tempfile.new(name)
|
77
|
+
t << text if text
|
78
|
+
TEMPFILES << t
|
79
|
+
result = t.path
|
80
|
+
end
|
81
|
+
|
82
|
+
result
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.close_tempfiles
|
86
|
+
TEMPFILES.map(&:close)
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.delete_tempfiles
|
90
|
+
TEMPFILES.map(&:delete)
|
91
|
+
end
|
92
|
+
|
93
|
+
# @param [String] path can be a file path or arbitrary string
|
94
|
+
# @return [Boolean] if the given string is a path to a file
|
95
|
+
def self.is_file_path?(path)
|
96
|
+
File.exist?(path)
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.run(args)
|
100
|
+
%x(
|
101
|
+
#{kdiff3_path} #{args}
|
102
|
+
)
|
103
|
+
$?.exitstatus
|
104
|
+
end
|
105
|
+
|
106
|
+
# ensures the local copy of kdiff3 is present, if not, download and compile it
|
107
|
+
def self.kdiff3_path
|
108
|
+
current_folder = File.dirname(__FILE__)
|
109
|
+
path = "#{current_folder}/../ext/kdiff3/releaseQt/kdiff3"
|
110
|
+
|
111
|
+
unless File.exist?(path)
|
112
|
+
build_kdiff3
|
113
|
+
end
|
114
|
+
|
115
|
+
path
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.build_kdiff3
|
119
|
+
current_folder = File.dirname(__FILE__)
|
120
|
+
kdiff3_repo_path = "#{current_folder}/../ext/kdiff3"
|
121
|
+
|
122
|
+
if File.exist?(kdiff3_repo_path)
|
123
|
+
%x(
|
124
|
+
git pull # origin optionally-fail-on-conflict
|
125
|
+
)
|
126
|
+
else
|
127
|
+
%x(
|
128
|
+
git clone git@github.com:NullVoxPopuli/kdiff3.git #{kdiff3_repo_path}
|
129
|
+
cd #{kdiff3_repo_path} && git checkout optionally-fail-on-conflict
|
130
|
+
)
|
131
|
+
end
|
132
|
+
|
133
|
+
# build
|
134
|
+
%x(
|
135
|
+
cd #{kdiff3_repo_path} && ./configure qt4
|
136
|
+
)
|
137
|
+
end
|
138
|
+
|
139
|
+
# add newlines after every tag, and every character
|
140
|
+
def self.add_new_lines(text)
|
141
|
+
text = self.add_new_lines_to_non_tags(text)
|
142
|
+
text = self.add_new_lines_after_tags(text)
|
143
|
+
|
144
|
+
# trim accidental blank lines
|
145
|
+
text.gsub!("#{NEWLINE}#{NEWLINE}", NEWLINE)
|
146
|
+
|
147
|
+
text
|
148
|
+
end
|
149
|
+
|
150
|
+
# http://www.rubular.com/r/N2AHZgpPum
|
151
|
+
# http://stackoverflow.com/questions/7540489/javascript-regex-match-text-not-part-of-a-html-tag
|
152
|
+
def self.add_new_lines_after_tags(text)
|
153
|
+
tag_selection_regex = /<[^>]*>/
|
154
|
+
text.gsub(tag_selection_regex) do |match|
|
155
|
+
match << NEWLINE
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# http://www.rubular.com/r/mpX6Ee2r0k
|
160
|
+
# http://stackoverflow.com/questions/18621568/regex-replace-text-outside-html-tags
|
161
|
+
def self.add_new_lines_to_non_tags(text)
|
162
|
+
non_tags = /(?<=^|>)[^><]+?(?=<|$)/
|
163
|
+
# non_tags = /([^<>]+)(?![^<]*>|[^<>]*<\/)/
|
164
|
+
text.gsub(non_tags) do |match|
|
165
|
+
# consecutive words
|
166
|
+
match.gsub!(" ", " #{NEWLINE}")
|
167
|
+
# end with NEWLINE
|
168
|
+
match << NEWLINE
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
end
|
data/spec/kdiff3_spec.rb
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe KDiff3 do
|
4
|
+
before(:each) do
|
5
|
+
if ENV['USE_BIN_KDIFF3']
|
6
|
+
current_folder = File.dirname(__FILE__)
|
7
|
+
# may only work on Ubuntu 14.04?
|
8
|
+
kdiff3_bin_path = "#{current_folder}/../bin/kdiff3"
|
9
|
+
allow(KDiff3).to receive(:kdiff3_path).and_return(kdiff3_bin_path)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
describe 'merge' do
|
15
|
+
it { expect{KDiff3.merge}.to raise_error(ArgumentError) }
|
16
|
+
it { expect{KDiff3.merge(base: "base")}.to raise_error(ArgumentError) }
|
17
|
+
it { expect{KDiff3.merge(base: "base", yours: "base")}.to_not raise_error }
|
18
|
+
it { expect{KDiff3.merge(base: "base", yours: "base", theirs: "base")}.to_not raise_error }
|
19
|
+
|
20
|
+
describe 'by line' do
|
21
|
+
before(:all) do
|
22
|
+
@base = "#{File.dirname(__FILE__)}/support/base"
|
23
|
+
@a = "#{File.dirname(__FILE__)}/support/a"
|
24
|
+
@b = "#{File.dirname(__FILE__)}/support/b"
|
25
|
+
@expected = "#{File.dirname(__FILE__)}/support/expected"
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'completes a 3-way merge' do
|
29
|
+
result = KDiff3.merge(
|
30
|
+
base: @base,
|
31
|
+
yours: @a,
|
32
|
+
theirs: @b
|
33
|
+
)
|
34
|
+
expected = IO.read(@expected)
|
35
|
+
|
36
|
+
expect(result).to eq expected
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe 'by word (the html option)' do
|
41
|
+
before(:all) do
|
42
|
+
@base = "<p>1,2,<p>1,<p>2,</p></p>3</p>"
|
43
|
+
@a = "<p>1,2,<p>1,</p>3</p>"
|
44
|
+
@b = "<p>1,2,<p><p>2,</p></p>3</p>"
|
45
|
+
@expected = "<p>1,2,<p></p>3</p>"
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'completes a 3-way merge' do
|
49
|
+
result = KDiff3.merge(
|
50
|
+
base: @base,
|
51
|
+
yours: @a,
|
52
|
+
theirs: @b,
|
53
|
+
html: true
|
54
|
+
)
|
55
|
+
expect(result).to eq @expected
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "raises an error if there are conflicts" do
|
60
|
+
it { expect{ KDiff3.merge(base: 'a b', yours: '1 b', theirs: '2 b', html: true) }.to raise_error }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe 'private methods' do
|
65
|
+
|
66
|
+
describe 'is_file?' do
|
67
|
+
it { expect(KDiff3.send(:is_file_path?, __FILE__)).to eq true }
|
68
|
+
it { expect(KDiff3.send(:is_file_path?, "#{File.dirname(__FILE__)}/spec_helper.rb")).to eq true }
|
69
|
+
it { expect(KDiff3.send(:is_file_path?, "not a file")).to eq false }
|
70
|
+
it { expect(KDiff3.send(:is_file_path?, "not/a/file")).to eq false }
|
71
|
+
end
|
72
|
+
|
73
|
+
describe 'tempfile' do
|
74
|
+
it 'returns path when path is a file' do
|
75
|
+
expect(KDiff3.send(:tempfile, text: __FILE__, name: 'rspec')).to eq __FILE__
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'creates a tempfile' do
|
79
|
+
expect{
|
80
|
+
KDiff3.send(:tempfile, name: 'base')
|
81
|
+
}.to change(KDiff3::TEMPFILES, :size).by 1
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'creates a tempfile with content' do
|
85
|
+
path = KDiff3.send(:tempfile, name: 'base', text: 'content')
|
86
|
+
# file must be closed before reading
|
87
|
+
KDiff3.send(:close_tempfiles)
|
88
|
+
expect(IO.read(path)).to eq 'content'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe 'kdiff3_path' do
|
93
|
+
it "hasn't compiled kdiff3 yet"
|
94
|
+
it "has compiled kdiff3"
|
95
|
+
it "updates the kdiff3 repo"
|
96
|
+
end
|
97
|
+
|
98
|
+
describe 'add_new_lines' do
|
99
|
+
after(:each) do
|
100
|
+
result = KDiff3.send(:add_new_lines, @text)
|
101
|
+
# remove special character
|
102
|
+
# - this determines our line breaks from pre-existing ones
|
103
|
+
result.gsub!(KDiff3::NEWLINE, "\n")
|
104
|
+
expect(result).to eq @expected
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'adds new lines to text' do
|
108
|
+
@text = "something like this"
|
109
|
+
@expected = "something \nlike \nthis\n"
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'adds new lines to html' do
|
113
|
+
@text = "<p>something maybe<p><span>like <a>this</a></span></p></p>"
|
114
|
+
@expected = "<p>\nsomething \nmaybe\n<p>\n<span>\nlike \n<a>\nthis\n</a>\n</span>\n</p>\n</p>\n"
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
require "rubygems"
|
3
|
+
require "bundler/setup"
|
4
|
+
|
5
|
+
require "pry-byebug" # binding.pry # to debug!
|
6
|
+
require 'awesome_print' # ap object # to pretty print
|
7
|
+
|
8
|
+
# Coverage
|
9
|
+
require "codeclimate-test-reporter"
|
10
|
+
# ENV['CODECLIMATE_REPO_TOKEN'] = ""
|
11
|
+
CodeClimate::TestReporter.start
|
12
|
+
|
13
|
+
# This Gem
|
14
|
+
require "kdiff3"
|
15
|
+
|
16
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
17
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
18
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
19
|
+
# loaded once.
|
20
|
+
#
|
21
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
22
|
+
RSpec.configure do |config|
|
23
|
+
config.run_all_when_everything_filtered = true
|
24
|
+
config.filter_run :focus
|
25
|
+
|
26
|
+
# Run specs in random order to surface order dependencies. If you find an
|
27
|
+
# order dependency and want to debug it, you can fix the order by providing
|
28
|
+
# the seed, which is printed after each run.
|
29
|
+
# --seed 1234
|
30
|
+
config.order = 'random'
|
31
|
+
end
|
data/spec/support/b
ADDED