rpatch 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +16 -7
- data/lib/rpatch/entry.rb +18 -12
- data/lib/rpatch/hunk.rb +181 -119
- data/lib/rpatch/patch.rb +29 -20
- data/lib/rpatch/runner.rb +5 -2
- data/lib/rpatch/utils.rb +121 -0
- data/lib/rpatch/version.rb +1 -1
- data/spec/patch_file_regexp_spec.rb +6 -8
- data/spec/patch_file_spec.rb +10 -3
- data/spec/patch_hunk_with_location_spec.rb +149 -0
- data/spec/patch_hunk_with_qmark_exp_spec.rb +152 -0
- data/spec/patch_hunk_with_regex_spec.rb +72 -0
- data/t/t0000-patch-file.sh +27 -28
- data/t/t0010-patch-special-direction.sh +336 -0
- data/t/t0100-patch-directory.sh +89 -42
- metadata +59 -73
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
data.tar.gz: 99bef45fa909f75a0680dbd94b5bc7fd2bb29b56
|
4
|
+
metadata.gz: 27d3cc372f56d16a39e4cde00e50cda8e890b191
|
5
|
+
SHA512:
|
6
|
+
data.tar.gz: 9fe2efe77726f1226e3fc72d353b789b50584c435fddb0f9bb2a8f074223c79b8051a0d5fd0183cb5dd965fe1150e1dd5964f71d9b2c700d6e28c058b593c8e6
|
7
|
+
metadata.gz: bf804d1fbcff8dac5c7877b3adfd3ffef762947eafe6d33366efd77627a8fcdd98b873253d8701f1ff36ca2050535ec6673ede954362a145a5b5b2db1b2b2d1a
|
data/README.md
CHANGED
@@ -4,12 +4,21 @@ rpatch: a patch utility support regexp in patchfile.
|
|
4
4
|
|
5
5
|
Rpatch is a patch utility, more tolerant than GNU patch. It will ignore
|
6
6
|
changes of blank lines and white spaces, and what's more you can write
|
7
|
-
regexp ("
|
7
|
+
regexp ("/ " and "/-") in patch file to match lines.
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
*
|
12
|
-
|
9
|
+
Three typical diff formats:
|
10
|
+
|
11
|
+
* Start with " ": exist in both original and target. (context)
|
12
|
+
* Start with "-": only in original file, not in target. (removed)
|
13
|
+
* Start with "+": not in original file, but in target. (added)
|
14
|
+
|
15
|
+
Additional diff formats that rpatch support:
|
16
|
+
|
17
|
+
* Start with "/ ": regexp which match both original and target.
|
18
|
+
* Start with "/-": regexp which match only original file.
|
19
|
+
* Start with "? ": in original file, but may not in target.
|
20
|
+
* Start with "?+": not in original file, but may or may not in target.
|
21
|
+
* Start with "?/ ": regexp which match original, but may not have in target.
|
13
22
|
|
14
23
|
For example:
|
15
24
|
|
@@ -31,8 +40,8 @@ For example:
|
|
31
40
|
+# Copyright (c) 2013 Jiang Xin
|
32
41
|
+
|
33
42
|
When I hack files using GNU patch,
|
34
|
-
|
35
|
-
|
43
|
+
/ [sS]ometimes fail because .*
|
44
|
+
/-(blah\s*){3}
|
36
45
|
@@ add notes
|
37
46
|
+If patch can ignore blank lines, support regex patterns
|
38
47
|
+in patch, it will be nice.
|
data/lib/rpatch/entry.rb
CHANGED
@@ -2,7 +2,9 @@
|
|
2
2
|
#
|
3
3
|
|
4
4
|
require 'stringio'
|
5
|
+
require 'fileutils'
|
5
6
|
require 'rpatch/error'
|
7
|
+
require 'rpatch/utils'
|
6
8
|
require 'rpatch/hunk'
|
7
9
|
|
8
10
|
module Rpatch
|
@@ -49,7 +51,7 @@ module Rpatch
|
|
49
51
|
''
|
50
52
|
end
|
51
53
|
rescue Exception => e
|
52
|
-
|
54
|
+
Tty.error e.message
|
53
55
|
end
|
54
56
|
|
55
57
|
def patch_on_file(input, output=nil)
|
@@ -59,6 +61,9 @@ module Rpatch
|
|
59
61
|
filename = "<#{newfile}>"
|
60
62
|
else
|
61
63
|
filename = output
|
64
|
+
unless File.exist?(File.dirname(output))
|
65
|
+
FileUtils.mkdir_p File.dirname(output)
|
66
|
+
end
|
62
67
|
end
|
63
68
|
|
64
69
|
if input.is_a? IO or input.is_a? StringIO
|
@@ -76,9 +81,9 @@ module Rpatch
|
|
76
81
|
begin
|
77
82
|
hunk.patch(lines)
|
78
83
|
rescue AlreadyPatchedError => e
|
79
|
-
|
84
|
+
Tty.info "#{filename}: #{e.message}"
|
80
85
|
rescue Exception => e
|
81
|
-
|
86
|
+
Tty.error "#{filename}: #{e.message}"
|
82
87
|
patch_status = false
|
83
88
|
else
|
84
89
|
patch_applied = true
|
@@ -88,7 +93,7 @@ module Rpatch
|
|
88
93
|
if output.is_a? IO or output.is_a? StringIO
|
89
94
|
output.write lines * "\n" + "\n"
|
90
95
|
elsif not patch_applied
|
91
|
-
|
96
|
+
Tty.notice "#{filename}: nothing changed"
|
92
97
|
if input != output
|
93
98
|
File.open(output, "w") do |io|
|
94
99
|
io.write lines * "\n" + "\n"
|
@@ -96,31 +101,32 @@ module Rpatch
|
|
96
101
|
end
|
97
102
|
else
|
98
103
|
unless patch_status
|
99
|
-
|
104
|
+
Tty.warning "saved orignal file as \"#{output}.orig\"."
|
100
105
|
File.open("#{output}.orig", "w") do |io|
|
101
106
|
io.write lines_dup * "\n" + "\n"
|
102
107
|
end
|
103
108
|
end
|
104
109
|
if lines.size > 0
|
105
|
-
STDERR.puts "Patched \"#{output}\"."
|
106
110
|
File.open(output, "w") do |io|
|
107
111
|
io.write lines * "\n" + "\n"
|
108
112
|
end
|
113
|
+
Tty.notice "Patched \"#{output}\"."
|
109
114
|
else
|
110
|
-
STDERR.puts "Remove \"#{output}\"."
|
111
115
|
File.unlink output
|
116
|
+
Tty.notice "Remove \"#{output}\"."
|
112
117
|
end
|
113
118
|
end
|
114
119
|
return patch_status
|
115
120
|
end
|
116
121
|
|
117
122
|
def patch_on_directory(inputdir, outputdir=nil)
|
118
|
-
outputdir ||=inputdir
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
+
outputdir ||= inputdir
|
124
|
+
if oldfile == '/dev/null'
|
125
|
+
input = newfile.start_with?('/') ? newfile : File.join(inputdir, newfile)
|
126
|
+
else
|
127
|
+
input = oldfile.start_with?('/') ? oldfile : File.join(inputdir, oldfile)
|
123
128
|
end
|
129
|
+
output = newfile.start_with?('/') ? newfile : File.join(outputdir, newfile)
|
124
130
|
patch_on_file(input, output)
|
125
131
|
end
|
126
132
|
|
data/lib/rpatch/hunk.rb
CHANGED
@@ -1,6 +1,88 @@
|
|
1
1
|
require 'rpatch/error'
|
2
2
|
|
3
3
|
module Rpatch
|
4
|
+
|
5
|
+
class Pattern
|
6
|
+
attr_reader :text
|
7
|
+
|
8
|
+
def initialize(line)
|
9
|
+
@text = line.chomp
|
10
|
+
end
|
11
|
+
|
12
|
+
def is_in_before?
|
13
|
+
@is_in_before ||= begin
|
14
|
+
@text =~ /^( |-|\/ |\/-|\? |\?-|\?\/ |\?\/-)/
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def is_in_after?
|
19
|
+
@is_in_after ||= begin
|
20
|
+
@text =~ /^( |\+|\/ |\/\+)/
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def is_add?
|
25
|
+
@is_add ||= begin
|
26
|
+
@text =~ /^\+/
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def is_pattern?
|
31
|
+
@is_pattern ||= pattern.is_a?(Regexp)
|
32
|
+
end
|
33
|
+
|
34
|
+
def pattern
|
35
|
+
@pattern ||= begin
|
36
|
+
if text =~ /^\/( |-|\+)/
|
37
|
+
Regexp.new(text[2..-1].strip)
|
38
|
+
elsif text =~ /^\?\/( |-|\+)/
|
39
|
+
Regexp.new(text[3..-1].strip)
|
40
|
+
elsif text =~ /^( |-|\+)/
|
41
|
+
pattern = text[1..-1].strip.gsub(/\s+/, ' ')
|
42
|
+
elsif text =~ /^\?( |-|\+)/
|
43
|
+
pattern = text[2..-1].strip.gsub(/\s+/, ' ')
|
44
|
+
else
|
45
|
+
raise PatchFormatError.new("-0---Unknown pattern in diffs: #{text.inspect}")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def match(message)
|
51
|
+
return nil unless message
|
52
|
+
message = message.chomp
|
53
|
+
match = nil
|
54
|
+
while true
|
55
|
+
if is_pattern?
|
56
|
+
# When match with regexp, do not twick message
|
57
|
+
if pattern.match(message)
|
58
|
+
match = 1
|
59
|
+
end
|
60
|
+
else
|
61
|
+
message = message.strip.gsub(/\s+/, ' ')
|
62
|
+
if pattern == message
|
63
|
+
match = 1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
if match
|
68
|
+
break
|
69
|
+
elsif message.empty?
|
70
|
+
match = 0
|
71
|
+
break
|
72
|
+
# compare without leading "#"
|
73
|
+
elsif message.start_with? "#"
|
74
|
+
while message.start_with? "#"
|
75
|
+
message = message[1..-1]
|
76
|
+
message = message.strip unless is_pattern?
|
77
|
+
end
|
78
|
+
else
|
79
|
+
break
|
80
|
+
end
|
81
|
+
end
|
82
|
+
match
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
4
86
|
class PatchHunk
|
5
87
|
attr_reader :title, :diffs, :num
|
6
88
|
|
@@ -11,7 +93,21 @@ module Rpatch
|
|
11
93
|
end
|
12
94
|
|
13
95
|
def feed_line(line)
|
14
|
-
diffs << line.chomp
|
96
|
+
@diffs << line.chomp
|
97
|
+
end
|
98
|
+
|
99
|
+
def head?
|
100
|
+
not tail?
|
101
|
+
end
|
102
|
+
|
103
|
+
def tail?
|
104
|
+
diffs.first == ">"
|
105
|
+
end
|
106
|
+
|
107
|
+
def has_add?
|
108
|
+
@has_add ||= begin
|
109
|
+
patterns.select{|p| p.is_add?}.any?
|
110
|
+
end
|
15
111
|
end
|
16
112
|
|
17
113
|
# Patch lines in place.
|
@@ -19,20 +115,14 @@ module Rpatch
|
|
19
115
|
matched_before = match_before_patch lines
|
20
116
|
matched_after = match_after_patch lines
|
21
117
|
|
22
|
-
if
|
118
|
+
if matched_after
|
23
119
|
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
120
|
raise AlreadyPatchedError.new("Hunk #{num} (#{title}) is already patched.")
|
33
|
-
elsif
|
34
|
-
raise
|
121
|
+
elsif has_add?
|
122
|
+
raise AlreadyPatchedError.new("Hunk #{num} (#{title}) is already patched.")
|
35
123
|
end
|
124
|
+
elsif not matched_before
|
125
|
+
raise PatchHunkError, "Hunk #{num} (#{title}) FAILED to apply. Match failed."
|
36
126
|
end
|
37
127
|
|
38
128
|
n, size = matched_before
|
@@ -43,41 +133,45 @@ module Rpatch
|
|
43
133
|
# Apply patch on lines.
|
44
134
|
# Return array of strings contain result of applying patch.
|
45
135
|
def convert(lines)
|
46
|
-
|
136
|
+
lines = lines.dup
|
47
137
|
result = []
|
48
|
-
i = 0
|
49
|
-
while i <
|
50
|
-
case
|
51
|
-
when
|
138
|
+
i = j = 0
|
139
|
+
while i < diffs.size
|
140
|
+
case diffs[i]
|
141
|
+
when /^\??\/? /
|
52
142
|
while true
|
53
|
-
match =
|
143
|
+
match = patterns[j].match lines.first
|
54
144
|
if not match
|
55
|
-
raise PatchFormatError.new("Hunk #{num} (#{title}) FAILED to apply. No match \"#{patterns[
|
145
|
+
raise PatchFormatError.new("Hunk #{num} (#{title}) FAILED to apply. No match \"#{patterns[j]}\" with #{lines.first.inspect}.")
|
56
146
|
elsif match > 0
|
57
|
-
result <<
|
147
|
+
result << lines.shift
|
58
148
|
break
|
59
149
|
elsif match == 0
|
60
|
-
result <<
|
150
|
+
result << lines.shift
|
61
151
|
end
|
62
152
|
end
|
63
|
-
when
|
153
|
+
when /^\??\/?[-]/
|
64
154
|
while true
|
65
|
-
match =
|
155
|
+
match = patterns[j].match lines.first
|
66
156
|
if not match
|
67
|
-
raise PatchFormatError.new("Hunk #{num} (#{title}) FAILED to apply. No match pattern \"#{patterns[
|
157
|
+
raise PatchFormatError.new("Hunk #{num} (#{title}) FAILED to apply. No match pattern \"#{patterns[j]}\" against #{lines.first.inspect}.")
|
68
158
|
elsif match > 0
|
69
|
-
|
159
|
+
lines.shift
|
70
160
|
break
|
71
161
|
elsif match == 0
|
72
|
-
|
162
|
+
lines.shift
|
73
163
|
end
|
74
164
|
end
|
75
|
-
when
|
76
|
-
result <<
|
165
|
+
when /^(\??\/?\+)/
|
166
|
+
result << diffs[i][$1.size..-1]
|
167
|
+
when /^(<|>)$/
|
168
|
+
# patterns do not have locaiton direction
|
169
|
+
j -= 1
|
77
170
|
else
|
78
|
-
raise PatchFormatError.new("Hunk #{num} (#{title}) FAILED to apply. Unknow syntax in hunk: #{
|
171
|
+
raise PatchFormatError.new("Hunk #{num} (#{title}) FAILED to apply. Unknow syntax in hunk: #{diffs[i]}")
|
79
172
|
end
|
80
173
|
i += 1
|
174
|
+
j += 1
|
81
175
|
end
|
82
176
|
result
|
83
177
|
end
|
@@ -86,19 +180,30 @@ module Rpatch
|
|
86
180
|
# match start with location and +num lines are matched.
|
87
181
|
# Return nil, if nothing matched.
|
88
182
|
def match_after_patch(lines)
|
89
|
-
i = 0
|
90
183
|
if patterns_after_patch.size == 0
|
91
|
-
return [0, 0]
|
184
|
+
return head? ? [0, 0] : [lines.size, 0]
|
92
185
|
end
|
93
186
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
187
|
+
if head?
|
188
|
+
i = 0
|
189
|
+
loop_n = lines.size - patterns_after_patch.size
|
190
|
+
while i <= loop_n
|
191
|
+
matched_size = get_matched_size(lines[i..-1], patterns_after_patch)
|
192
|
+
if matched_size
|
193
|
+
return [i, matched_size]
|
194
|
+
else
|
195
|
+
i += 1
|
196
|
+
end
|
197
|
+
end
|
198
|
+
else
|
199
|
+
i = lines.size - patterns_after_patch.size
|
200
|
+
while i >= 0
|
201
|
+
matched_size = get_matched_size(lines[i..-1], patterns_after_patch)
|
202
|
+
if matched_size
|
203
|
+
return [i, matched_size]
|
204
|
+
else
|
205
|
+
i -= 1
|
206
|
+
end
|
102
207
|
end
|
103
208
|
end
|
104
209
|
nil
|
@@ -108,20 +213,38 @@ module Rpatch
|
|
108
213
|
# at location, and +num lines would be replaced.
|
109
214
|
# Return nil, if nothing matched.
|
110
215
|
def match_before_patch(lines)
|
111
|
-
|
216
|
+
# puts "="*90
|
217
|
+
# puts patterns_before_patch.inspect
|
218
|
+
# puts patterns_before_patch.size
|
219
|
+
# puts "-"*90
|
220
|
+
# puts diffs.inspect
|
221
|
+
#puts "-"*90
|
222
|
+
#puts patterns.map{|p| [p.pattern.inspect, p.is_in_before?.inspect, p.is_in_after?.inspect]}.inspect
|
112
223
|
if patterns_before_patch.size == 0
|
113
|
-
return [0, 0]
|
224
|
+
return head? ? [0, 0] : [lines.size, 0]
|
114
225
|
end
|
115
226
|
|
116
|
-
|
117
|
-
|
227
|
+
if head?
|
228
|
+
i = 0
|
229
|
+
loop_n = lines.size - patterns_before_patch.size
|
118
230
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
231
|
+
while i <= loop_n
|
232
|
+
matched_size = get_matched_size(lines[i..-1], patterns_before_patch)
|
233
|
+
if matched_size
|
234
|
+
return [i, matched_size]
|
235
|
+
else
|
236
|
+
i += 1
|
237
|
+
end
|
238
|
+
end
|
239
|
+
else
|
240
|
+
i = lines.size - patterns_before_patch.size
|
241
|
+
while i >= 0
|
242
|
+
matched_size = get_matched_size(lines[i..-1], patterns_before_patch)
|
243
|
+
if matched_size
|
244
|
+
return [i, matched_size]
|
245
|
+
else
|
246
|
+
i -= 1
|
247
|
+
end
|
125
248
|
end
|
126
249
|
end
|
127
250
|
nil
|
@@ -130,7 +253,7 @@ module Rpatch
|
|
130
253
|
# Test whether patterns match against the beginning of lines
|
131
254
|
# Return nil if not match, or return number of lines matched
|
132
255
|
# with patterns (would be replaced later).
|
133
|
-
def
|
256
|
+
def get_matched_size(lines, patterns)
|
134
257
|
i = 0
|
135
258
|
found = true
|
136
259
|
patterns.each do |pattern|
|
@@ -140,7 +263,7 @@ module Rpatch
|
|
140
263
|
end
|
141
264
|
|
142
265
|
while true
|
143
|
-
match =
|
266
|
+
match = pattern.match lines[i]
|
144
267
|
if not match
|
145
268
|
found = false
|
146
269
|
break
|
@@ -177,90 +300,29 @@ module Rpatch
|
|
177
300
|
end
|
178
301
|
end
|
179
302
|
|
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
303
|
def patterns_before_patch
|
216
304
|
@patterns_before_patch ||= begin
|
217
|
-
|
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
|
305
|
+
patterns.select{|p| p.is_in_before?}
|
231
306
|
end
|
232
307
|
end
|
233
308
|
|
234
309
|
def patterns_after_patch
|
235
310
|
@patterns_after_patch ||= begin
|
236
|
-
|
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
|
311
|
+
patterns.select{|p| p.is_in_after?}
|
250
312
|
end
|
251
313
|
end
|
252
314
|
|
253
315
|
def patterns
|
254
316
|
@patterns ||= begin
|
255
317
|
result = []
|
256
|
-
|
318
|
+
diffs.each do |line|
|
257
319
|
case line
|
258
|
-
when /^( |\+|-)/
|
259
|
-
result << line[1..-1].strip.gsub(/\s+/, ' ')
|
260
320
|
when /^(RE: |RE:-)/
|
261
|
-
|
321
|
+
raise PatchFormatError.new("Obsolete pattern, subsitude \"RE:\" with \"/\":\n=> #{line}")
|
322
|
+
when /^(<|>)$/
|
323
|
+
# ignore locaiton direction
|
262
324
|
else
|
263
|
-
|
325
|
+
result << Pattern.new(line)
|
264
326
|
end
|
265
327
|
end
|
266
328
|
result
|