git-patch-patch 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/git-patch-patch.rb +5 -0
- data/lib/git-patch-patch/patch_patcher.rb +225 -0
- data/lib/git-patch-patch/version.rb +9 -0
- metadata +112 -0
@@ -0,0 +1,225 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# from stdlib
|
4
|
+
require 'pathname'
|
5
|
+
require 'forwardable'
|
6
|
+
|
7
|
+
# from me
|
8
|
+
require 'git-trifle'
|
9
|
+
require 'path-accessor'
|
10
|
+
|
11
|
+
# this class knows how make a diff and
|
12
|
+
# write it to a retrievable patch
|
13
|
+
# how to apply a patch and commit it
|
14
|
+
module Git
|
15
|
+
|
16
|
+
class Trifle
|
17
|
+
|
18
|
+
# not too bright though
|
19
|
+
class PatchPatcher
|
20
|
+
|
21
|
+
# clean API best bud
|
22
|
+
extend Forwardable
|
23
|
+
|
24
|
+
# what we'll use from git-trifle
|
25
|
+
def_delegators :@t, :commits, :clone, :checkout
|
26
|
+
|
27
|
+
# current patch generated by the last diff performed
|
28
|
+
attr_reader :patch
|
29
|
+
|
30
|
+
# open the repo in path
|
31
|
+
# set the root path from where the patches are retrieved
|
32
|
+
def initialize(options)
|
33
|
+
options = {branch: 'master', patch_dir: '/tmp'}.merge options
|
34
|
+
|
35
|
+
# git handler
|
36
|
+
@t = Git::Trifle.new options[:repo]
|
37
|
+
# where the patch file will be stored
|
38
|
+
@patch_dir = Pathstring.new options[:patch_dir]
|
39
|
+
# branch from which the commits will be reviewed
|
40
|
+
@branch = options[:branch]
|
41
|
+
# list of reviewed commits
|
42
|
+
@patcher_commits = commits branch: @branch
|
43
|
+
end
|
44
|
+
|
45
|
+
def patch_work(pattern, replacement, *options, &block)
|
46
|
+
# work branch
|
47
|
+
checkout_work_branch
|
48
|
+
|
49
|
+
# main workflow
|
50
|
+
@patcher_commits.each_with_index do |c, id|
|
51
|
+
# exit on the last commit
|
52
|
+
break unless c_next = @patcher_commits[id + 1]
|
53
|
+
|
54
|
+
# current diff sets the current patch
|
55
|
+
diff c, c_next
|
56
|
+
|
57
|
+
# even if work is done we yield the patch
|
58
|
+
# to allow modification
|
59
|
+
patch.work == :done &&
|
60
|
+
yield(patch)
|
61
|
+
|
62
|
+
# if a patch file is found, we commit and
|
63
|
+
# jump to next iteration
|
64
|
+
patch.file.exist? &&
|
65
|
+
commit_patch(&block) &&
|
66
|
+
next
|
67
|
+
|
68
|
+
# patch filenames and / or patch content
|
69
|
+
options.each do |work|
|
70
|
+
patch.send "patch_#{work}", pattern, replacement
|
71
|
+
yield patch
|
72
|
+
end
|
73
|
+
|
74
|
+
# save to file and commit
|
75
|
+
patch.save
|
76
|
+
commit_patch &block
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# this class base operation
|
81
|
+
# a diff between two sha's stored in @patch
|
82
|
+
def diff(first_commit, second_commit)
|
83
|
+
@patch = PatchPatch.new @t.diff(first_commit, second_commit),
|
84
|
+
first_commit: first_commit,
|
85
|
+
second_commit: second_commit,
|
86
|
+
file: patch_file_for(second_commit),
|
87
|
+
work: patch_file_for(second_commit).exist?
|
88
|
+
end
|
89
|
+
|
90
|
+
# plain as plain :
|
91
|
+
# - patch from file
|
92
|
+
# - apply and commit with a reuse of the commit message
|
93
|
+
# from the second sha of the diff which generated the patch
|
94
|
+
def commit_patch
|
95
|
+
patch.error = nil
|
96
|
+
|
97
|
+
@t.apply patch.file
|
98
|
+
@t.add '.'
|
99
|
+
@t.commit '', reuse_message: patch.second_commit
|
100
|
+
rescue => error
|
101
|
+
# Houston we have a problem
|
102
|
+
patch.error = error
|
103
|
+
# first we yield to allow a fix
|
104
|
+
yield patch if block_given?
|
105
|
+
|
106
|
+
# if a fix was made, retry
|
107
|
+
if patch.changed?
|
108
|
+
patch.save
|
109
|
+
retry
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def checkout_work_branch
|
114
|
+
# checkout -b barbaric_name sha
|
115
|
+
checkout "__patch_patcher_#{Time.now.to_f}", commit: @patcher_commits.first
|
116
|
+
end
|
117
|
+
|
118
|
+
# unique patch filename for a given sha and a given local repo'
|
119
|
+
def patch_file_for(commit)
|
120
|
+
@patch_dir.join 'git-patch-patch',
|
121
|
+
# horrendous but necessary to ensure unicity
|
122
|
+
# of path, without having an absolute path
|
123
|
+
# that 'join' doesn't like
|
124
|
+
Pathname(@t.directory).realpath.to_s.sub('/',''),
|
125
|
+
commit.to_s,
|
126
|
+
'patch'
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
# patch model as a String for its primary
|
132
|
+
# interface, but it knows a lot more
|
133
|
+
class PatchPatch < String
|
134
|
+
|
135
|
+
# file handling
|
136
|
+
extend PathAccessor
|
137
|
+
|
138
|
+
path_accessor :file
|
139
|
+
attr_accessor :first_commit, :second_commit, :work, :error
|
140
|
+
|
141
|
+
# quick list of Strings methods that perform in-place
|
142
|
+
# modification
|
143
|
+
# we install a simple shunt on these methods to implement
|
144
|
+
# the changed? method
|
145
|
+
|
146
|
+
# :!= is left here by design to have changed? answer true
|
147
|
+
# the first time it is called after a possible change
|
148
|
+
instance_methods.grep(/\!/).push(:<<).each do |meth|
|
149
|
+
define_method meth do |*args, &block|
|
150
|
+
# record previous state
|
151
|
+
@previous = self.to_s
|
152
|
+
# call to vanilla
|
153
|
+
super *args, &block
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# defines patch_patch and patch_filenames
|
158
|
+
%w|filenames patch|.each do |work|
|
159
|
+
define_method "patch_#{work}".to_sym do |pattern, replacement|
|
160
|
+
# the actual job on patch
|
161
|
+
send work.to_sym, pattern, replacement
|
162
|
+
|
163
|
+
# changed to avoid messing with changed? the user
|
164
|
+
# could call
|
165
|
+
changed = self.to_s != @previous.to_s
|
166
|
+
@work = changed ? work.to_sym : :nothing
|
167
|
+
changed # return value says if it did anything
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def initialize(string, attributes=nil)
|
172
|
+
# instance attributes in one line
|
173
|
+
(attributes ||= {}).each { |k, v| send "#{k}=", v }
|
174
|
+
|
175
|
+
# job done ? ok then...
|
176
|
+
if @work == true || @work == :done
|
177
|
+
# give work the value we want
|
178
|
+
@work = :done
|
179
|
+
string = file.read
|
180
|
+
end
|
181
|
+
|
182
|
+
# changes handler
|
183
|
+
@previous = string.to_s
|
184
|
+
|
185
|
+
# vanilla
|
186
|
+
super string
|
187
|
+
end
|
188
|
+
|
189
|
+
def changed?
|
190
|
+
self != @previous
|
191
|
+
end
|
192
|
+
|
193
|
+
# to file
|
194
|
+
def save
|
195
|
+
# trailing \n or git complains
|
196
|
+
file.save!(self + "\n")
|
197
|
+
end
|
198
|
+
|
199
|
+
# patch content from file
|
200
|
+
def reload_from_file
|
201
|
+
@previous = self.to_s
|
202
|
+
replace file.read
|
203
|
+
end
|
204
|
+
|
205
|
+
private
|
206
|
+
|
207
|
+
# naughty nasty operation on filenames in patch
|
208
|
+
def filenames(pattern, replacement)
|
209
|
+
# we trap what begins with a:/ or b/ until we find the pattern
|
210
|
+
# it's replaced by what's captured with replacement appended
|
211
|
+
gsub! /(a|b)\/([^\n]*?)#{pattern}/, "\\1/\\2#{replacement}"
|
212
|
+
end
|
213
|
+
|
214
|
+
# Now this not pretty : we allow ourselves to alter anything
|
215
|
+
# in the patch according to pattern/replacement couple.
|
216
|
+
# Welcome conflictorama
|
217
|
+
def patch(pattern, replacement)
|
218
|
+
gsub! pattern, replacement
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: git-patch-patch
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- lacravate
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-18 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: getopt
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: git-trifle
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: path-accessor
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: A script to rewrite git patches/commits, while keeping commits history
|
79
|
+
email:
|
80
|
+
- lacravate@lacravate.fr
|
81
|
+
executables: []
|
82
|
+
extensions: []
|
83
|
+
extra_rdoc_files: []
|
84
|
+
files:
|
85
|
+
- lib/git-patch-patch.rb
|
86
|
+
- lib/git-patch-patch/patch_patcher.rb
|
87
|
+
- lib/git-patch-patch/version.rb
|
88
|
+
homepage: https://github.com/lacravate/git-patch-patch
|
89
|
+
licenses: []
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ! '>='
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ! '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project: ! '[none]'
|
108
|
+
rubygems_version: 1.8.24
|
109
|
+
signing_key:
|
110
|
+
specification_version: 3
|
111
|
+
summary: A script to rewrite git patches/commits, while keeping commits history
|
112
|
+
test_files: []
|