git-patch-patch 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+
3
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
4
+
5
+ require 'git-patch-patch/patch_patcher'
@@ -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
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ module Git
4
+ class Trifle
5
+ class PatchPatcher
6
+ VERSION = "0.0.1"
7
+ end
8
+ end
9
+ 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: []