gem-patch 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # gem-patch
2
+
3
+ A RubyGems plugin that patches gems.
4
+
5
+ ## Description
6
+
7
+ `gem-patch` is a RubyGems plugin that helps to patch gems without manually opening and rebuilding them. It opens a given .gem file, extracts it, patches it with system `patch` command, clones its spec, updates the file list and builds the patched gem.
8
+
9
+ ## Usage
10
+
11
+ `gem patch [options] name-version.gem PATCH [PATCH ...]`
12
+
13
+ Optionally with `-pNUMBER` or `--strip=NUMBER` option that sets the file name strip count to NUMBER
14
+ (same options as for `patch` command on Linux machines).
15
+
16
+ ## Requirements
17
+
18
+ This version is build for both RubyGems 1.8 and RubyGems 2.0.
data/README.rdoc ADDED
@@ -0,0 +1,18 @@
1
+ = gem-patch
2
+
3
+ A RubyGems plugin that patches gems.
4
+
5
+ == Description
6
+
7
+ `gem-patch` is a RubyGems plugin that helps to patch gems without manually opening and rebuilding them. It opens a given .gem file, extracts it, patches it with system `patch` command, clones its spec, updates the file list and builds the patched gem.
8
+
9
+ == Usage
10
+
11
+ `gem patch [options] name-version.gem PATCH [PATCH ...]`
12
+
13
+ Optionally with `-pNUMBER` or `--strip=NUMBER` option that sets the file name strip count to NUMBER
14
+ (same options as for `patch` command on Linux machines).
15
+
16
+ == Requirements
17
+
18
+ This version is build for RubyGems 2.0.a, a branch for RubyGems 1.8 is also available.
@@ -0,0 +1,54 @@
1
+ require "rubygems/command"
2
+ require "rubygems/patcher"
3
+
4
+ class Gem::Commands::PatchCommand < Gem::Command
5
+ def initialize
6
+ super "patch", "Patches the gem with the given patches and generates patched gem.",
7
+ :output => Dir.pwd, :strip => 0
8
+
9
+ # Same as 'patch -pNUMBER' on Linux machines
10
+ add_option('-pNUMBER', '--strip=NUMBER', 'Set the file name strip count to NUMBER.') do |number, options|
11
+ options[:strip] = number
12
+ end
13
+ end
14
+
15
+ def arguments # :nodoc:
16
+ args = <<-EOF
17
+ GEMFILE path to the gem file to patch
18
+ PATCH [PATCH ...] list of patches to apply
19
+ EOF
20
+ return args.gsub(/^\s+/, '')
21
+ end
22
+
23
+ def description # :nodoc:
24
+ <<-EOF
25
+ `gem-patch` is a RubyGems plugin that helps to patch gems without manually opening and rebuilding them.
26
+ It opens a given .gem file, extracts it, patches it with system "patch" command,
27
+ clones its spec, updates the file list and builds the patched gem.
28
+ EOF
29
+ end
30
+
31
+ def usage # :nodoc:
32
+ "#{program_name} GEMFILE PATCH [PATCH ...]"
33
+ end
34
+
35
+ def execute
36
+ gemfile = options[:args].shift
37
+ patches = options[:args]
38
+
39
+ # No gem
40
+ unless gemfile
41
+ raise Gem::CommandLineError,
42
+ "Please specify a gem file on the command line (e.g. gem patch foo-0.1.0.gem PATCH [PATCH ...])"
43
+ end
44
+
45
+ # No patches
46
+ if patches.empty?
47
+ raise Gem::CommandLineError,
48
+ "Please specify patches to apply (e.g. gem patch foo-0.1.0.gem foo.patch bar.patch ...)"
49
+ end
50
+
51
+ patcher = Gem::Patcher.new(gemfile, options[:output])
52
+ patcher.patch_with(patches, options[:strip])
53
+ end
54
+ end
@@ -0,0 +1,31 @@
1
+ require "rubygems/installer"
2
+ require "rubygems/builder"
3
+
4
+ ##
5
+ # Simulate RubyGems 2.0 behavior to use master branch of gem-patch plugin with RubyGems 1.8
6
+
7
+ module Gem::Package
8
+ def self.new gem
9
+ @gem = gem
10
+ self
11
+ end
12
+
13
+ def self.extract_files dir
14
+ @installer = Gem::Installer.new @gem
15
+ @installer.unpack dir
16
+ @spec = @installer.spec
17
+ end
18
+
19
+ def self.build skip_validation=false
20
+ @builder = Gem::Builder.new @spec
21
+ @builder.build
22
+ end
23
+
24
+ def self.spec=(spec)
25
+ @spec = spec
26
+ end
27
+
28
+ def self.spec
29
+ @spec
30
+ end
31
+ end
@@ -0,0 +1,128 @@
1
+ require "rbconfig"
2
+ require "tmpdir"
3
+ require "rubygems/package"
4
+
5
+ class Gem::Patcher
6
+ include Gem::UserInteraction
7
+
8
+ if Gem::VERSION < '2.0'
9
+ require "rubygems/package-1.8"
10
+ end
11
+
12
+ class PatchCommandMissing < StandardError; end
13
+
14
+ def initialize(gemfile, output_dir)
15
+ @gemfile = gemfile
16
+ @output_dir = output_dir
17
+
18
+ # @target_dir is a temporary directory where the gem files live
19
+ tmpdir = Dir.mktmpdir
20
+ basename = File.basename(gemfile, '.gem')
21
+ @target_dir = File.join(tmpdir, basename)
22
+ end
23
+
24
+ ##
25
+ # Patch the gem, move the new patched gem to the working directory and return the path
26
+
27
+ def patch_with(patches, strip_number)
28
+ check_patch_command_is_installed
29
+ extract_gem
30
+
31
+ # Apply all patches
32
+ patches.each do |patch|
33
+ info 'Applying patch ' + patch
34
+ apply_patch(patch, strip_number)
35
+ end
36
+
37
+ build_patched_gem
38
+
39
+ # Move the newly generated gem to the working directory
40
+ gem_file = IO.read(File.join @target_dir, @package.spec.file_name)
41
+
42
+ File.open(File.join(@output_dir, @package.spec.file_name), 'w') do |f|
43
+ f.write gem_file
44
+ end
45
+
46
+ # Return the path to the patched gem
47
+ File.join @output_dir, "#{@package.spec.file_name}"
48
+ end
49
+
50
+ def apply_patch(patch, strip_number)
51
+ patch_path = File.expand_path(patch)
52
+ info 'Path to the patch to apply: ' + patch_path
53
+
54
+ # Apply the patch by calling 'patch -pNUMBER < patch'
55
+ Dir.chdir @target_dir do
56
+ if system("patch --verbose -p#{strip_number} < #{patch_path}")
57
+ info 'Succesfully patched by ' + patch
58
+ else
59
+ info 'Error: Unable to patch with ' + patch
60
+ end
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def extract_gem
67
+ @package = Gem::Package.new @gemfile
68
+
69
+ # Unpack
70
+ info "Unpacking gem '#{@gemfile}' in " + @target_dir
71
+ @package.extract_files @target_dir
72
+ end
73
+
74
+ def build_patched_gem
75
+ patched_package = Gem::Package.new @package.spec.file_name
76
+ patched_package.spec = @package.spec.clone
77
+ patched_package.spec.files = files_in_gem
78
+
79
+ # Change dir and build the patched gem
80
+ Dir.chdir @target_dir do
81
+ patched_package.build false
82
+ end
83
+ end
84
+
85
+ def info(msg)
86
+ say msg if Gem.configuration.verbose
87
+ end
88
+
89
+ def files_in_gem
90
+ files = []
91
+
92
+ Dir.foreach(@target_dir) do |file|
93
+ if File.directory? File.join @target_dir, file
94
+ files += files_in_dir(file) unless /\./.match(file)
95
+ else
96
+ files << file
97
+ end
98
+ end
99
+
100
+ delete_original_files(files)
101
+ end
102
+
103
+ def files_in_dir(dir)
104
+ files = []
105
+
106
+ Dir.foreach(File.join @target_dir, dir) do |file|
107
+ if File.directory? File.join @target_dir, dir, file
108
+ files += files_in_dir(File.join dir, file) unless /\./.match(file)
109
+ else
110
+ files << File.join(dir, file)
111
+ end
112
+ end
113
+
114
+ files
115
+ end
116
+
117
+ def delete_original_files(files)
118
+ files.each do |file|
119
+ files.delete file if /\.orig/.match(file)
120
+ end
121
+ end
122
+
123
+ def check_patch_command_is_installed
124
+ unless system("patch --version")
125
+ raise PatchCommandMissing, 'Calling `patch` command failed. Do you have it installed?'
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,2 @@
1
+ require "rubygems/command_manager"
2
+ Gem::CommandManager.instance.register_command :patch
data/rakefile.rb ADDED
@@ -0,0 +1,38 @@
1
+ require 'rubygems/package_task'
2
+ require 'rake/testtask'
3
+ require 'rdoc/task'
4
+
5
+ gemspec = Gem::Specification.new do |s|
6
+ s.name = "gem-patch"
7
+ s.version = "0.1.1"
8
+ s.platform = Gem::Platform::RUBY
9
+ s.summary = "RubyGems plugin for patching gems."
10
+ s.description = <<-EOF
11
+ `gem-patch` is a RubyGems plugin that helps to patch gems without manually opening and rebuilding them.
12
+ It opens a given .gem file, extracts it, patches it with system `patch` command,
13
+ clones its spec, updates the file list and builds the patched gem.
14
+ EOF
15
+ s.licenses = ["MIT"]
16
+ s.author = "Josef Stribny"
17
+ s.email = "jstribny@redhat.com"
18
+ s.required_ruby_version = ">= 1.8.7"
19
+ s.required_rubygems_version = ">= 1.8.0"
20
+ s.files = FileList["README.md", "README.rdoc", "rakefile.rb",
21
+ "lib/**/*.rb", "test/**/test*.rb"]
22
+ end
23
+
24
+ Gem::PackageTask.new gemspec do |pkg|
25
+ end
26
+
27
+ Rake::RDocTask.new do |rd|
28
+ rd.main = "README.rdoc"
29
+ rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
30
+ end
31
+
32
+ Rake::TestTask.new('test') do |t|
33
+ t.libs << 'test'
34
+ t.pattern = 'test/**/test*.rb'
35
+ t.verbose = true
36
+ end
37
+
38
+ task :default => [:test]
@@ -0,0 +1,34 @@
1
+ require "rubygems/test_case"
2
+ require "rubygems/commands/patch_command"
3
+
4
+ class TestGemCommandsPatchCommand < Gem::TestCase
5
+ def setup
6
+ super
7
+
8
+ @command = Gem::Commands::PatchCommand.new
9
+ end
10
+
11
+ def test_execute_no_gemfile
12
+ @command.options[:args] = []
13
+
14
+ e = assert_raises Gem::CommandLineError do
15
+ use_ui @ui do
16
+ @command.execute
17
+ end
18
+ end
19
+
20
+ assert_match 'Please specify a gem file on the command line (e.g. gem patch foo-0.1.0.gem PATCH [PATCH ...])', e.message
21
+ end
22
+
23
+ def test_execute_no_patch
24
+ @command.options[:args] = ['Gemfile.gem']
25
+
26
+ e = assert_raises Gem::CommandLineError do
27
+ use_ui @ui do
28
+ @command.execute
29
+ end
30
+ end
31
+
32
+ assert_match 'Please specify patches to apply (e.g. gem patch foo-0.1.0.gem foo.patch bar.patch ...)', e.message
33
+ end
34
+ end
@@ -0,0 +1,285 @@
1
+ require "rubygems/test_case"
2
+ require "rubygems/patcher"
3
+
4
+ class TestGemPatch < Gem::TestCase
5
+ def setup
6
+ super
7
+
8
+ @gems_dir = File.join @tempdir, 'gems'
9
+ @lib_dir = File.join @tempdir, 'gems', 'lib'
10
+ FileUtils.mkdir_p @lib_dir
11
+ end
12
+
13
+ ##
14
+ # Test changing a file in a gem with -p1 option
15
+
16
+ def test_change_file_patch
17
+ gemfile = bake_testing_gem
18
+
19
+ patches = []
20
+ patches << bake_change_file_patch
21
+
22
+ # Creates new patched gem in @gems_dir
23
+ patcher = Gem::Patcher.new(gemfile, @gems_dir)
24
+ patched_gem = patcher.patch_with(patches, 1)
25
+
26
+ # Unpack
27
+ package = Gem::Package.new patched_gem
28
+ package.extract_files @gems_dir
29
+
30
+ assert_equal patched_file, file_contents('foo.rb')
31
+ end
32
+
33
+ ##
34
+ # Test adding a file into a gem with -p0 option
35
+
36
+ def test_new_file_patch
37
+ gemfile = bake_testing_gem
38
+
39
+ patches = []
40
+ patches << bake_new_file_patch
41
+
42
+ # Create a new patched gem in @gems_fir
43
+ patcher = Gem::Patcher.new(gemfile, @gems_dir)
44
+ patched_gem = patcher.patch_with(patches, 0)
45
+
46
+ # Unpack
47
+ package = Gem::Package.new patched_gem
48
+ package.extract_files @gems_dir
49
+
50
+ assert_equal original_file, file_contents('bar.rb')
51
+ end
52
+
53
+ ##
54
+ # Test adding and deleting a file in a gem with -p0 option
55
+
56
+ def test_delete_file_patch
57
+ gemfile = bake_testing_gem
58
+
59
+ patches = []
60
+ patches << bake_new_file_patch
61
+ patches << bake_delete_file_patch
62
+
63
+ # Create a new patched gem in @gems_fir
64
+ patcher = Gem::Patcher.new(gemfile, @gems_dir)
65
+ patched_gem = patcher.patch_with(patches, 0)
66
+
67
+ # Unpack
68
+ package = Gem::Package.new patched_gem
69
+ package.extract_files @gems_dir
70
+
71
+ # Only foo.rb should stay in /lib, bar.rb should be gone
72
+ assert_raises(RuntimeError, 'File not found') {
73
+ file_contents(File.join @lib_dir, 'bar.rb')
74
+ }
75
+ end
76
+
77
+ ##
78
+ # Incorrect patch, nothing happens
79
+
80
+ def test_gem_should_not_change
81
+ gemfile = bake_testing_gem
82
+
83
+ patches = []
84
+ patches << bake_incorrect_patch
85
+
86
+ # Create a new patched gem in @gems_fir
87
+ patcher = Gem::Patcher.new(gemfile, @gems_dir)
88
+ patched_gem = patcher.patch_with(patches, 0)
89
+
90
+ # Unpack
91
+ package = Gem::Package.new patched_gem
92
+ package.extract_files @gems_dir
93
+
94
+ assert_equal original_file, file_contents('foo.rb')
95
+ assert_equal original_gemspec, current_gemspec
96
+ end
97
+
98
+ def bake_change_file_patch
99
+ patch_path = File.join(@gems_dir, 'change_file.patch')
100
+
101
+ File.open(patch_path, 'w') do |f|
102
+ f.write change_file_patch
103
+ end
104
+
105
+ patch_path
106
+ end
107
+
108
+ def bake_new_file_patch
109
+ patch_path = File.join(@gems_dir, 'new_file.patch')
110
+
111
+ File.open(patch_path, 'w') do |f|
112
+ f.write new_file_patch
113
+ end
114
+
115
+ patch_path
116
+ end
117
+
118
+ def bake_delete_file_patch
119
+ patch_path = File.join(@gems_dir, 'delete_file.patch')
120
+
121
+ File.open(patch_path, 'w') do |f|
122
+ f.write delete_file_patch
123
+ end
124
+
125
+ patch_path
126
+ end
127
+
128
+ def bake_incorrect_patch
129
+ patch_path = File.join(@gems_dir, 'incorrect.patch')
130
+
131
+ File.open(patch_path, 'w') do |f|
132
+ f.write incorrect_patch
133
+ end
134
+
135
+ patch_path
136
+ end
137
+
138
+ def bake_original_gem_files
139
+ # Create /lib/foo.rb
140
+ file_path = File.join(@lib_dir, 'foo.rb')
141
+
142
+ File.open(file_path, 'w') do |f|
143
+ f.write original_file
144
+ end
145
+
146
+ # Create .gemspec file
147
+ gemspec_path = File.join(@gems_dir, 'foo-0.gemspec')
148
+
149
+ File.open(gemspec_path, 'w') do |f|
150
+ f.write original_gemspec
151
+ end
152
+ end
153
+
154
+ def bake_testing_gem
155
+ bake_original_gem_files
156
+
157
+ test_package = Gem::Package.new 'foo-0.gem'
158
+ test_package.spec = Gem::Specification.load(File.join(@gems_dir, 'foo-0.gemspec'))
159
+
160
+ # Build
161
+ Dir.chdir @gems_dir do
162
+ test_package.build false
163
+ end
164
+
165
+ File.join(@gems_dir, 'foo-0.gem')
166
+ end
167
+
168
+ def current_gemspec
169
+ gemspec_path = File.join(@gems_dir, 'foo-0.gemspec')
170
+
171
+ IO.read(gemspec_path)
172
+ end
173
+
174
+ ##
175
+ # Get the content of the given file in @lib_dir
176
+
177
+ def file_contents(file)
178
+ file_path = File.join(@lib_dir, file)
179
+
180
+ begin
181
+ file_content = IO.read(file_path)
182
+ rescue
183
+ raise RuntimeError, 'File not found'
184
+ end
185
+
186
+ file_content
187
+ end
188
+
189
+ def original_gemspec
190
+ <<-EOF
191
+ Gem::Specification.new do |s|
192
+ s.platform = Gem::Platform::RUBY
193
+ s.name = 'foo'
194
+ s.version = 0
195
+ s.author = 'A User'
196
+ s.email = 'example@example.com'
197
+ s.homepage = 'http://example.com'
198
+ s.summary = "this is a summary"
199
+ s.description = "This is a test description"
200
+ s.files = ['lib/foo.rb']
201
+ end
202
+ EOF
203
+ end
204
+
205
+ def original_file
206
+ <<-EOF
207
+ module Foo
208
+ def bar
209
+ 'Original'
210
+ end
211
+ end
212
+ EOF
213
+ end
214
+
215
+ def patched_file
216
+ <<-EOF
217
+ module Foo
218
+ class Bar
219
+ def foo_bar
220
+ 'Patched'
221
+ end
222
+ end
223
+ end
224
+ EOF
225
+ end
226
+
227
+ def change_file_patch
228
+ <<-EOF
229
+ diff -u a/lib/foo.rb b/lib/foo.rb
230
+ --- a/lib/foo.rb
231
+ +++ b/lib/foo.rb
232
+ @@ -1,6 +1,8 @@
233
+ module Foo
234
+ - def bar
235
+ - 'Original'
236
+ + class Bar
237
+ + def foo_bar
238
+ + 'Patched'
239
+ + end
240
+ end
241
+ end
242
+ EOF
243
+ end
244
+
245
+ def new_file_patch
246
+ <<-EOF
247
+ diff lib/bar.rb lib/bar.rb
248
+ --- /dev/null
249
+ +++ lib/bar.rb
250
+ @@ -0,0 +1,5 @@
251
+ + module Foo
252
+ + def bar
253
+ + 'Original'
254
+ + end
255
+ + end
256
+ EOF
257
+ end
258
+
259
+ def delete_file_patch
260
+ <<-EOF
261
+ diff lib/bar.rb lib/bar.rb
262
+ --- lib/bar.rb
263
+ +++ /dev/null
264
+ @@ -1,5 +0,0 @@
265
+ - module Foo
266
+ - def bar
267
+ - 'Original'
268
+ - end
269
+ - end
270
+ EOF
271
+ end
272
+
273
+ def incorrect_patch
274
+ <<-EOF
275
+ diff lib/foo.rb lib/foo.rb
276
+ --- lib/foo.rb
277
+ +++ /dev/null
278
+ - module Foo
279
+ - def bar
280
+ - 'Original'
281
+ - end
282
+ - end
283
+ EOF
284
+ end
285
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gem-patch
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Josef Stribny
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-02 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: ! " `gem-patch` is a RubyGems plugin that helps to
15
+ patch gems without manually opening and rebuilding them.\n It
16
+ opens a given .gem file, extracts it, patches it with system `patch` command,\n
17
+ \ clones its spec, updates the file list and builds the patched
18
+ gem.\n"
19
+ email: jstribny@redhat.com
20
+ executables: []
21
+ extensions: []
22
+ extra_rdoc_files: []
23
+ files:
24
+ - README.md
25
+ - README.rdoc
26
+ - rakefile.rb
27
+ - lib/rubygems_plugin.rb
28
+ - lib/rubygems/package-1.8.rb
29
+ - lib/rubygems/patcher.rb
30
+ - lib/rubygems/commands/patch_command.rb
31
+ - test/rubygems/test_gem_patch.rb
32
+ - test/rubygems/test_gem_commands_patch_command.rb
33
+ homepage:
34
+ licenses:
35
+ - MIT
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 1.8.7
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: 1.8.0
52
+ requirements: []
53
+ rubyforge_project:
54
+ rubygems_version: 1.8.24
55
+ signing_key:
56
+ specification_version: 3
57
+ summary: RubyGems plugin for patching gems.
58
+ test_files: []