rpatch 0.0.1 → 0.0.2
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.
- 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
|