rpatch 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +22 -0
- data/README.md +90 -0
- data/bin/rpatch +27 -0
- data/lib/rpatch/entry.rb +137 -0
- data/lib/rpatch/error.rb +8 -0
- data/lib/rpatch/hunk.rb +271 -0
- data/lib/rpatch/patch.rb +158 -0
- data/lib/rpatch/runner.rb +44 -0
- data/lib/rpatch/version.rb +3 -0
- data/spec/patch_entry_spec.rb +234 -0
- data/spec/patch_file_regexp_spec.rb +73 -0
- data/spec/patch_file_spec.rb +267 -0
- data/spec/patch_hunk_spec.rb +272 -0
- data/spec/spec_helper.rb +24 -0
- data/t/Makefile +80 -0
- data/t/README +3 -0
- data/t/aggregate-results.sh +46 -0
- data/t/t0000-patch-file.sh +297 -0
- data/t/t0100-patch-directory.sh +203 -0
- data/t/test-lib-functions.sh +722 -0
- data/t/test-lib.sh +623 -0
- metadata +132 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Jiang Xin
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# Rpatch
|
2
|
+
|
3
|
+
rpatch: a patch utility support regexp in patchfile.
|
4
|
+
|
5
|
+
Rpatch is a patch utility, more tolerant than GNU patch. It will ignore
|
6
|
+
changes of blank lines and white spaces, and what's more you can write
|
7
|
+
regexp ("RE: " and "RE:-") in patch file to match lines.
|
8
|
+
|
9
|
+
* Use "RE: " with a regexp to match unchanged line.
|
10
|
+
* Use "RE:-" with a regexp to match removed line.
|
11
|
+
* Use "@@" to starts a new patch hunk only and use it's text as
|
12
|
+
description for the hunk only.
|
13
|
+
|
14
|
+
For example:
|
15
|
+
|
16
|
+
* Origninal file:
|
17
|
+
|
18
|
+
When I hack files using GNU patch,
|
19
|
+
sometimes fail because of the change of upstream files.
|
20
|
+
blah blah blah...
|
21
|
+
So comes rpatch.
|
22
|
+
|
23
|
+
Happy hacking.
|
24
|
+
|
25
|
+
* Patch file:
|
26
|
+
|
27
|
+
diff -ru a/README b/README
|
28
|
+
--- a/readme.txt 2013-11-03 22:17:02.000000000 +0800
|
29
|
+
+++ b/readme.txt 2013-11-03 21:10:46.000000000 +0800
|
30
|
+
@@ add copyright at the beginning
|
31
|
+
+# Copyright (c) 2013 Jiang Xin
|
32
|
+
+
|
33
|
+
When I hack files using GNU patch,
|
34
|
+
RE: sometimes fail because .*
|
35
|
+
RE:-(blah\s*){3}
|
36
|
+
@@ add notes
|
37
|
+
+If patch can ignore blank lines, support regex patterns
|
38
|
+
+in patch, it will be nice.
|
39
|
+
So comes rpatch.
|
40
|
+
@@ add signature
|
41
|
+
Happy hacking.
|
42
|
+
+--
|
43
|
+
+jiangxin
|
44
|
+
|
45
|
+
* And the result would be:
|
46
|
+
|
47
|
+
# Copyright (c) 2013 Jiang Xin
|
48
|
+
|
49
|
+
When I hack files using GNU patch,
|
50
|
+
sometimes fail because of the change of upstream files.
|
51
|
+
If patch can ignore blank lines, support regex patterns
|
52
|
+
in patch, it will be nice.
|
53
|
+
So comes rpatch.
|
54
|
+
|
55
|
+
Happy hacking.
|
56
|
+
--
|
57
|
+
jiangxin
|
58
|
+
|
59
|
+
## Installation
|
60
|
+
|
61
|
+
Install using rubygems:
|
62
|
+
|
63
|
+
$ gem install rpatch
|
64
|
+
|
65
|
+
Or install from source:
|
66
|
+
|
67
|
+
$ rake build
|
68
|
+
$ rake install
|
69
|
+
|
70
|
+
## Usage
|
71
|
+
|
72
|
+
Rpatch is a GNU patch likely utilty, patch original file using patch file like this:
|
73
|
+
|
74
|
+
$ patch originalfile patchfile
|
75
|
+
|
76
|
+
Or patch files under the current directory, and read patchfile from STDIN:
|
77
|
+
|
78
|
+
$ patch -p1 < patchfile
|
79
|
+
|
80
|
+
Rpatch can also read a series patches in quilt format.
|
81
|
+
|
82
|
+
$ patch -p1 . patches
|
83
|
+
|
84
|
+
## Contributing
|
85
|
+
|
86
|
+
1. Fork it
|
87
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
88
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
89
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
90
|
+
5. Create new Pull Request
|
data/bin/rpatch
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rpatch/runner'
|
6
|
+
Rpatch::Runner.runner
|
7
|
+
rescue LoadError
|
8
|
+
$stderr.puts <<-EOS
|
9
|
+
#{'*'*50}
|
10
|
+
Could not require 'rpatch/patch'
|
11
|
+
|
12
|
+
This may happen if you're using rubygems as your package manager, but it is not
|
13
|
+
being required through some mechanism before executing the rpatch command.
|
14
|
+
|
15
|
+
You may need to do one of the following in your shell:
|
16
|
+
|
17
|
+
# for bash/zsh
|
18
|
+
export RUBYOPT=rubygems
|
19
|
+
|
20
|
+
# for csh, etc.
|
21
|
+
set RUBYOPT=rubygems
|
22
|
+
|
23
|
+
For background, please see http://gist.github.com/54177.
|
24
|
+
#{'*'*50}
|
25
|
+
EOS
|
26
|
+
exit(1)
|
27
|
+
end
|
data/lib/rpatch/entry.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
|
4
|
+
require 'stringio'
|
5
|
+
require 'rpatch/error'
|
6
|
+
require 'rpatch/hunk'
|
7
|
+
|
8
|
+
module Rpatch
|
9
|
+
class PatchEntry
|
10
|
+
attr_reader :level
|
11
|
+
|
12
|
+
def initialize(old, new, level)
|
13
|
+
@entry_old = old
|
14
|
+
@entry_new = new
|
15
|
+
@level = level
|
16
|
+
@hunks = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def oldfile
|
20
|
+
@oldfile ||= path_strip_prefix(@entry_old, @level)
|
21
|
+
end
|
22
|
+
|
23
|
+
def newfile
|
24
|
+
@newfile ||= path_strip_prefix(@entry_new, @level)
|
25
|
+
end
|
26
|
+
|
27
|
+
def feed_line(line)
|
28
|
+
if line =~ /^@@/
|
29
|
+
hunk = PatchHunk.new line, @hunks.size + 1
|
30
|
+
@hunks << hunk
|
31
|
+
elsif @hunks.any?
|
32
|
+
@hunks[-1].feed_line line
|
33
|
+
else
|
34
|
+
raise PatchFormatError, "Feed lines must start with @@, not: #{line}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Used in test cases.
|
39
|
+
def patch_on_message(text)
|
40
|
+
lines = text.split("\n").map{|line| line.chomp}
|
41
|
+
|
42
|
+
@hunks.each do |hunk|
|
43
|
+
hunk.patch(lines)
|
44
|
+
end
|
45
|
+
|
46
|
+
if lines.size > 0
|
47
|
+
lines * "\n" + "\n"
|
48
|
+
else
|
49
|
+
''
|
50
|
+
end
|
51
|
+
rescue Exception => e
|
52
|
+
STDERR.puts "Error: #{e.message}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def patch_on_file(input, output=nil)
|
56
|
+
lines = []
|
57
|
+
output ||= input
|
58
|
+
if output.is_a? IO or output.is_a? StringIO
|
59
|
+
filename = "<#{newfile}>"
|
60
|
+
else
|
61
|
+
filename = output
|
62
|
+
end
|
63
|
+
|
64
|
+
if input.is_a? IO or input.is_a? StringIO
|
65
|
+
lines = input.readlines.map{|line| line.chomp}
|
66
|
+
elsif File.file? input
|
67
|
+
File.open(input) do |io|
|
68
|
+
lines = io.readlines.map{|line| line.chomp}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
lines_dup = lines.dup
|
72
|
+
|
73
|
+
patch_applied = false
|
74
|
+
patch_status = true
|
75
|
+
@hunks.each do |hunk|
|
76
|
+
begin
|
77
|
+
hunk.patch(lines)
|
78
|
+
rescue AlreadyPatchedError => e
|
79
|
+
STDERR.puts "#{filename}: #{e.message}"
|
80
|
+
rescue Exception => e
|
81
|
+
STDERR.puts "ERROR: #{filename}: #{e.message}"
|
82
|
+
patch_status = false
|
83
|
+
else
|
84
|
+
patch_applied = true
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
if output.is_a? IO or output.is_a? StringIO
|
89
|
+
output.write lines * "\n" + "\n"
|
90
|
+
elsif not patch_applied
|
91
|
+
STDERR.puts "#{filename}: nothing changed"
|
92
|
+
if input != output
|
93
|
+
File.open(output, "w") do |io|
|
94
|
+
io.write lines * "\n" + "\n"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
else
|
98
|
+
unless patch_status
|
99
|
+
STDERR.puts "Warning: saved orignal file as \"#{output}.orig\"."
|
100
|
+
File.open("#{output}.orig", "w") do |io|
|
101
|
+
io.write lines_dup * "\n" + "\n"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
if lines.size > 0
|
105
|
+
STDERR.puts "Patched \"#{output}\"."
|
106
|
+
File.open(output, "w") do |io|
|
107
|
+
io.write lines * "\n" + "\n"
|
108
|
+
end
|
109
|
+
else
|
110
|
+
STDERR.puts "Remove \"#{output}\"."
|
111
|
+
File.unlink output
|
112
|
+
end
|
113
|
+
end
|
114
|
+
return patch_status
|
115
|
+
end
|
116
|
+
|
117
|
+
def patch_on_directory(inputdir, outputdir=nil)
|
118
|
+
outputdir ||=inputdir
|
119
|
+
input = oldfile.start_with?('/') ? oldfile : File.join(inputdir, oldfile)
|
120
|
+
output = outputdir
|
121
|
+
if File.directory? outputdir
|
122
|
+
output = newfile.start_with?('/') ? newfile : File.join(outputdir, newfile)
|
123
|
+
end
|
124
|
+
patch_on_file(input, output)
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def path_strip_prefix(name, level)
|
130
|
+
filename = name.dup
|
131
|
+
unless filename.start_with? '/'
|
132
|
+
level.times {filename.sub!(/^[^\/]+\//, '')}
|
133
|
+
end
|
134
|
+
filename
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
data/lib/rpatch/error.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
module Rpatch
|
2
|
+
class AlreadyPatchedError < Exception; end
|
3
|
+
class PatchHunkError < Exception; end
|
4
|
+
class PatchFormatError < Exception; end
|
5
|
+
class FileNotExistError < Exception; end
|
6
|
+
class PatchFailNotify < Exception; end
|
7
|
+
class PatchOneWithManyError < Exception; end
|
8
|
+
end
|
data/lib/rpatch/hunk.rb
ADDED
@@ -0,0 +1,271 @@
|
|
1
|
+
require 'rpatch/error'
|
2
|
+
|
3
|
+
module Rpatch
|
4
|
+
class PatchHunk
|
5
|
+
attr_reader :title, :diffs, :num
|
6
|
+
|
7
|
+
def initialize(text, num=nil)
|
8
|
+
@title = text.chomp.sub(/^@@/, '').strip
|
9
|
+
@num = num || "#"
|
10
|
+
@diffs = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def feed_line(line)
|
14
|
+
diffs << line.chomp
|
15
|
+
end
|
16
|
+
|
17
|
+
# Patch lines in place.
|
18
|
+
def patch(lines)
|
19
|
+
matched_before = match_before_patch lines
|
20
|
+
matched_after = match_after_patch lines
|
21
|
+
|
22
|
+
if patterns_before_patch.size > patterns_after_patch.size
|
23
|
+
if not matched_before
|
24
|
+
if matched_after
|
25
|
+
raise AlreadyPatchedError.new("Hunk #{num} (#{title}) is already patched.")
|
26
|
+
else
|
27
|
+
raise PatchHunkError, "Hunk #{num} (#{title}) FAILED to apply. Match failed."
|
28
|
+
end
|
29
|
+
end
|
30
|
+
else
|
31
|
+
if matched_after
|
32
|
+
raise AlreadyPatchedError.new("Hunk #{num} (#{title}) is already patched.")
|
33
|
+
elsif not matched_before
|
34
|
+
raise PatchHunkError, "Hunk #{num} (#{title}) FAILED to apply. Match failed."
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
n, size = matched_before
|
39
|
+
lines[n...(n+size)] = convert lines[n...(n+size)]
|
40
|
+
lines
|
41
|
+
end
|
42
|
+
|
43
|
+
# Apply patch on lines.
|
44
|
+
# Return array of strings contain result of applying patch.
|
45
|
+
def convert(lines)
|
46
|
+
lines_dup = lines.dup
|
47
|
+
result = []
|
48
|
+
i = 0
|
49
|
+
while i < @diffs.size
|
50
|
+
case @diffs[i]
|
51
|
+
when /^( |RE: )/
|
52
|
+
while true
|
53
|
+
match = match_line lines_dup.first, patterns[i]
|
54
|
+
if not match
|
55
|
+
raise PatchFormatError.new("Hunk #{num} (#{title}) FAILED to apply. No match \"#{patterns[i]}\" with #{lines_dup.first.inspect}.")
|
56
|
+
elsif match > 0
|
57
|
+
result << lines_dup.shift
|
58
|
+
break
|
59
|
+
elsif match == 0
|
60
|
+
result << lines_dup.shift
|
61
|
+
end
|
62
|
+
end
|
63
|
+
when /^(-|RE:-)/
|
64
|
+
while true
|
65
|
+
match = match_line lines_dup.first, patterns[i]
|
66
|
+
if not match
|
67
|
+
raise PatchFormatError.new("Hunk #{num} (#{title}) FAILED to apply. No match pattern \"#{patterns[i]}\" against #{lines_dup.first.inspect}.")
|
68
|
+
elsif match > 0
|
69
|
+
lines_dup.shift
|
70
|
+
break
|
71
|
+
elsif match == 0
|
72
|
+
lines_dup.shift
|
73
|
+
end
|
74
|
+
end
|
75
|
+
when /^\+/
|
76
|
+
result << @diffs[i][1..-1]
|
77
|
+
else
|
78
|
+
raise PatchFormatError.new("Hunk #{num} (#{title}) FAILED to apply. Unknow syntax in hunk: #{@diffs[i]}")
|
79
|
+
end
|
80
|
+
i += 1
|
81
|
+
end
|
82
|
+
result
|
83
|
+
end
|
84
|
+
|
85
|
+
# Return [location, +num_of_lines], which seems already patched, and
|
86
|
+
# match start with location and +num lines are matched.
|
87
|
+
# Return nil, if nothing matched.
|
88
|
+
def match_after_patch(lines)
|
89
|
+
i = 0
|
90
|
+
if patterns_after_patch.size == 0
|
91
|
+
return [0, 0]
|
92
|
+
end
|
93
|
+
|
94
|
+
loop_n = lines.size
|
95
|
+
loop_n = loop_n - patterns_after_patch.size + 1 if patterns_after_patch.size > 0
|
96
|
+
while i < loop_n
|
97
|
+
matched_line_no = match_beginning(lines[i..-1], patterns_after_patch)
|
98
|
+
if matched_line_no
|
99
|
+
return [i, matched_line_no]
|
100
|
+
else
|
101
|
+
i += 1
|
102
|
+
end
|
103
|
+
end
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
|
107
|
+
# Return [location, +num_of_lines], which could apply patch at
|
108
|
+
# at location, and +num lines would be replaced.
|
109
|
+
# Return nil, if nothing matched.
|
110
|
+
def match_before_patch(lines)
|
111
|
+
i = 0
|
112
|
+
if patterns_before_patch.size == 0
|
113
|
+
return [0, 0]
|
114
|
+
end
|
115
|
+
|
116
|
+
loop_n = lines.size
|
117
|
+
loop_n = loop_n - patterns_before_patch.size + 1 if patterns_before_patch.size > 0
|
118
|
+
|
119
|
+
while i < loop_n
|
120
|
+
matched_size = match_beginning(lines[i..-1], patterns_before_patch)
|
121
|
+
if matched_size
|
122
|
+
return [i, matched_size]
|
123
|
+
else
|
124
|
+
i += 1
|
125
|
+
end
|
126
|
+
end
|
127
|
+
nil
|
128
|
+
end
|
129
|
+
|
130
|
+
# Test whether patterns match against the beginning of lines
|
131
|
+
# Return nil if not match, or return number of lines matched
|
132
|
+
# with patterns (would be replaced later).
|
133
|
+
def match_beginning(lines, patterns)
|
134
|
+
i = 0
|
135
|
+
found = true
|
136
|
+
patterns.each do |pattern|
|
137
|
+
unless lines[i]
|
138
|
+
found = false
|
139
|
+
break
|
140
|
+
end
|
141
|
+
|
142
|
+
while true
|
143
|
+
match = match_line lines[i], pattern
|
144
|
+
if not match
|
145
|
+
found = false
|
146
|
+
break
|
147
|
+
# Match precisely for the first line of pattern (i==0),
|
148
|
+
# Not pass blank lines.
|
149
|
+
elsif match == 0 and i == 0
|
150
|
+
found = false
|
151
|
+
break
|
152
|
+
# Matched.
|
153
|
+
elsif match > 0
|
154
|
+
break
|
155
|
+
# Match next line if this line is blank.
|
156
|
+
elsif match == 0
|
157
|
+
i += 1
|
158
|
+
unless lines[i]
|
159
|
+
found = false
|
160
|
+
break
|
161
|
+
end
|
162
|
+
# Never comes here.
|
163
|
+
else
|
164
|
+
found = false
|
165
|
+
break
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
break unless found
|
170
|
+
i += 1
|
171
|
+
end
|
172
|
+
|
173
|
+
if found
|
174
|
+
i
|
175
|
+
else
|
176
|
+
nil
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def match_line(message , pattern)
|
181
|
+
return nil unless message
|
182
|
+
line = nil
|
183
|
+
match = nil
|
184
|
+
while true
|
185
|
+
if pattern.is_a? Regexp
|
186
|
+
# When match with regexp, do not twick message
|
187
|
+
line ||= message.dup
|
188
|
+
if pattern.match(line)
|
189
|
+
match = 1
|
190
|
+
end
|
191
|
+
else
|
192
|
+
line ||= message.strip.gsub(/\s+/, ' ')
|
193
|
+
if pattern == line
|
194
|
+
match = 1
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
if match
|
199
|
+
break
|
200
|
+
elsif line.empty?
|
201
|
+
match = 0
|
202
|
+
break
|
203
|
+
elsif line.start_with? "#"
|
204
|
+
while line.start_with? "#"
|
205
|
+
line = line[1..-1]
|
206
|
+
line = line.strip unless pattern.is_a? Regexp
|
207
|
+
end
|
208
|
+
else
|
209
|
+
break
|
210
|
+
end
|
211
|
+
end
|
212
|
+
match
|
213
|
+
end
|
214
|
+
|
215
|
+
def patterns_before_patch
|
216
|
+
@patterns_before_patch ||= begin
|
217
|
+
result = []
|
218
|
+
@diffs.each do |line|
|
219
|
+
case line
|
220
|
+
when /^( |-)/
|
221
|
+
result << line[1..-1].strip.gsub(/\s+/, ' ')
|
222
|
+
when /^(RE: |RE:-)/
|
223
|
+
result << Regexp.new(line[4..-1].strip)
|
224
|
+
when /^\+/
|
225
|
+
next
|
226
|
+
else
|
227
|
+
raise PatchFormatError.new("Unknown pattern in diffs: #{line}")
|
228
|
+
end
|
229
|
+
end
|
230
|
+
result
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def patterns_after_patch
|
235
|
+
@patterns_after_patch ||= begin
|
236
|
+
result = []
|
237
|
+
@diffs.each do |line|
|
238
|
+
case line
|
239
|
+
when /^( |\+)/
|
240
|
+
result << line[1..-1].strip.gsub(/\s+/, ' ')
|
241
|
+
when /^(RE: )/
|
242
|
+
result << Regexp.new(line[4..-1].strip)
|
243
|
+
when /^(-|RE:-)/
|
244
|
+
next
|
245
|
+
else
|
246
|
+
raise PatchFormatError.new("Unknown pattern in diffs: #{line}")
|
247
|
+
end
|
248
|
+
end
|
249
|
+
result
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def patterns
|
254
|
+
@patterns ||= begin
|
255
|
+
result = []
|
256
|
+
@diffs.each do |line|
|
257
|
+
case line
|
258
|
+
when /^( |\+|-)/
|
259
|
+
result << line[1..-1].strip.gsub(/\s+/, ' ')
|
260
|
+
when /^(RE: |RE:-)/
|
261
|
+
result << Regexp.new(line[4..-1].strip)
|
262
|
+
else
|
263
|
+
raise PatchFormatError.new("Unknown pattern in diffs: #{line}")
|
264
|
+
end
|
265
|
+
end
|
266
|
+
result
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
end
|
271
|
+
end
|
data/lib/rpatch/patch.rb
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
|
4
|
+
require 'rpatch/error'
|
5
|
+
require 'rpatch/entry'
|
6
|
+
|
7
|
+
module Rpatch
|
8
|
+
class Patch
|
9
|
+
attr_reader :name, :level, :patch_entries
|
10
|
+
|
11
|
+
class <<self
|
12
|
+
def apply(path, patches, patch_level)
|
13
|
+
unless File.exist? path
|
14
|
+
raise FileNotExistError, "File or directory \"#{path}\" does not exist"
|
15
|
+
end
|
16
|
+
patch_status = true
|
17
|
+
patches.each do |patch_file|
|
18
|
+
if patch_file.is_a? IO or patch_file.is_a? StringIO
|
19
|
+
apply_one(path, patch_file, patch_level) || patch_status = false
|
20
|
+
elsif File.file? patch_file
|
21
|
+
apply_one(path, patch_file, patch_level) || patch_status = false
|
22
|
+
elsif File.directory? patch_file
|
23
|
+
apply_quilt(path, patch_file, patch_level) || patch_status = false
|
24
|
+
else
|
25
|
+
raise FileNotExistError, "Can not find patch file: #{patch_file}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
patch_status
|
29
|
+
rescue Exception => e
|
30
|
+
STDERR.puts "Error: #{e.message}"
|
31
|
+
patch_status = false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(file, level)
|
36
|
+
@name = file.is_a?(String) ? file : "<#{file.class.to_s}>"
|
37
|
+
@patch = file
|
38
|
+
@level = level
|
39
|
+
@patch_entries = []
|
40
|
+
load_patch
|
41
|
+
end
|
42
|
+
|
43
|
+
def apply_to(input, output=nil)
|
44
|
+
patch_status = true
|
45
|
+
patch_entries.each do |patch_entry|
|
46
|
+
begin
|
47
|
+
# input maybe a IO, StringIO, directory, file, or in-exist file.
|
48
|
+
if input.is_a? String and File.directory? input
|
49
|
+
patch_entry.patch_on_directory(input, output) || patch_status = false
|
50
|
+
else
|
51
|
+
if patch_entries.size > 1
|
52
|
+
raise PatchOneWithManyError, "Multiple patch entries (#{patch_entries.size}) have been found in patch #{name}"
|
53
|
+
end
|
54
|
+
# a IO, StringIO, file, or inexist file.
|
55
|
+
patch_entry.patch_on_file(input, output) || patch_status = false
|
56
|
+
end
|
57
|
+
rescue Exception => e
|
58
|
+
STDERR.puts "Error: #{e.message}"
|
59
|
+
patch_status = false
|
60
|
+
end
|
61
|
+
end
|
62
|
+
patch_status
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
class <<self
|
68
|
+
def apply_one(path, patch_file, patch_level)
|
69
|
+
patch = Patch.new(patch_file, patch_level)
|
70
|
+
patch.apply_to(path)
|
71
|
+
end
|
72
|
+
|
73
|
+
def apply_quilt(path, quilt_dir, patch_level)
|
74
|
+
patch_status = true
|
75
|
+
if File.exist?("#{quilt_dir}/series")
|
76
|
+
File.open("#{quilt_dir}/series") do |io|
|
77
|
+
io.readlines.each do |line|
|
78
|
+
line.strip!
|
79
|
+
filename = line
|
80
|
+
level = patch_level
|
81
|
+
if line =~ /^(.*?)[\s]+-p([0-9]*)$/
|
82
|
+
filename = $1
|
83
|
+
level = $2
|
84
|
+
end
|
85
|
+
unless filename.start_with? '#'
|
86
|
+
apply_one(path, filename, level) || patch_status = false
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
else
|
91
|
+
raise FileNotExistError, "Can not find (quilt) patchs in dir: #{quilt_dir}"
|
92
|
+
end
|
93
|
+
patch_status
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def load_patch
|
98
|
+
i=0
|
99
|
+
lines = get_patch
|
100
|
+
while i < lines.size
|
101
|
+
entry_names = find_new_entry(lines[i..i+6])
|
102
|
+
if entry_names
|
103
|
+
@patch_entries << PatchEntry.new(entry_names[0], entry_names[1], @level)
|
104
|
+
while not lines[i] =~ /^@@ / and lines[i]
|
105
|
+
i += 1
|
106
|
+
end
|
107
|
+
break unless i < lines.size
|
108
|
+
end
|
109
|
+
|
110
|
+
if @patch_entries.any?
|
111
|
+
if lines[i] =~ /^(@@| |-|\+|RE: |RE:-)/
|
112
|
+
@patch_entries.last.feed_line lines[i]
|
113
|
+
elsif lines[i] =~ /^(Binary files |Only in)/
|
114
|
+
# ignore
|
115
|
+
else
|
116
|
+
raise PatchFormatError, "Line #{i} of patch \"#{name}\" is invalid.\n\t=> #{lines[i].inspect}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
i += 1
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def get_patch
|
125
|
+
if @patch.is_a? IO or @patch.is_a? StringIO
|
126
|
+
@patch.readlines.map {|line| line.chomp}
|
127
|
+
else
|
128
|
+
File.open(@patch) do |io|
|
129
|
+
io.readlines.map {|line| line.chomp}
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def find_new_entry(lines)
|
135
|
+
return nil if lines[0] =~ /^(@@| |-|\+|RE: |RE:-)/
|
136
|
+
old = new = nil
|
137
|
+
lines_dup = lines.dup
|
138
|
+
line = lines_dup.shift
|
139
|
+
line = lines_dup.shift if line =~ /^diff /
|
140
|
+
line = lines_dup.shift if line =~ /^new file mode/
|
141
|
+
line = lines_dup.shift if line =~ /^index /
|
142
|
+
if line =~ /^--- (.+?)([\s]+[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}.*)?$/
|
143
|
+
old = $1
|
144
|
+
line = lines_dup.shift
|
145
|
+
end
|
146
|
+
if line =~ /^\+\+\+ (.+?)([\s]+[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}.*)?$/
|
147
|
+
new = $1
|
148
|
+
line = lines_dup.shift
|
149
|
+
end
|
150
|
+
if line =~ /^@@ / and old and new
|
151
|
+
[old, new]
|
152
|
+
else
|
153
|
+
nil
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|