rpatch 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.
- 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
|